alxctools索引超出了数组界限_[译]V8中的数组类型

译者:蒋海涛

JavaScript 对象可以和任何属性有关联。对象属性的名称可以包含任何字符。有趣的是 JavaScript 引擎可以选择名称为纯数字的属性来进行优化,而这个属性其实就是数组 index。

在 V8 中,会特殊处理整数名称的属性(最常见的形式是由 Array 构造函数生成的对象)。尽管很多情况下这些数字索引属性的表现和其他属性一样,但为了优化,V8 将它们和非数字属性分开存储。在内部,V8 甚至给这些属性一个特殊的名称:elements。对象通过properties可以 map 到一些 value ,而数组通过 index 可以 map 到一些子元素。

尽管这些内部细节从来没有直接向 JavaScript 开发人员公开,但它们解释了为什么某些代码模式比其他模式更快。

常见的 elements 类型

在运行 JavaScript 代码时,V8 会追踪每个数组的 elements 的类型。V8 可以根据这些信息,在对拥有这种 elements 类型的数组进行操作时,进行针对性的优化。例如,当在数组上调用 reducemapforEach 时,V8 可以根据数组的 elements 类型来优化这些操作。

以这个数组为例:

const array = [1, 2, 3];

这个数组的 elements 类型是什么呢?如果用 typeof 来回答,结果就是这个数组含有 number 类型的数。在语言层面,这就是我们能看到的:JavaScript 不会区分整数(integers),浮点数(floats)和双精度数(doubles),它们都只是数字。但在引擎层面,我们可以做更精确地区分。该数组的 elements 类型为 PACKED_SMI_ELEMENTS。在 V8 中,术语 Smi 是指用于存储小整数(small integers)的一种特定格式。

然后,向同一个数组中添加浮点数会把这个数组转为更通用的 elements 类型

const array = [1, 2, 3];
// elements 类型: PACKED_SMI_ELEMENTS
array.push(4.56);
// elements 类型: PACKED_DOUBLE_ELEMENTS

向数组中添加字符串将再次改变数组 elements 类型

const array = [1, 2, 3];
// elements 类型: PACKED_SMI_ELEMENTS
array.push(4.56);
// elements 类型: PACKED_DOUBLE_ELEMENTS
array.push('x');
// elements 类型: PACKED_ELEMENTS

到目前为止,我们已经看到了 3 种不同的 elements 类型,以下是基本类型

  • Sm all i ntegers,也就是 Smi
  • Doubles,用于不能用 Smi 表示的浮点数(floating-point)和整数(integers)
  • 常规 elements,用于不能表示为 Smi 或双精度值(doubles)的值

注意,doubles 是 Smi 的一种更通用的变体,常规 elements 是 doubles 之上的另一种泛化。用 Smi 表示的数字集 是 用 double 表示的数字集的子集。

重点是 elements 类型只向一个方向转化,从特殊的(比如: PACKED_SMI_ELEMENTS) 转向更常规的(比如: PACKED_ELEMENTS),比如一旦一个数组被标记为是 PACKED_ELEMENTS,它就不能再转化成 PACKED_DOUBLE_ELEMENTS 类型的了。

目前,我们已经了解到

  • V8 会对每个数组赋予一个 elements 类型
  • 数组的 elements 类型并不是一成不变的 —— 它可以在运行时改变。之前的例子中有从 PACKED_SMI_ELEMENTS 转向 PACKED_ELEMENTS
  • elements 类型只能从特定类型转向常规类型

PACKEDHOLEY 类型

目前我们只谈到了 packed 类型的数组。在数组中创建 holes (使数组变稀疏)会将其 elements 类型降级成它的 "holey" 版本

const array = [1, 2, 3, 4.56, 'x'];
// elements 类型: PACKED_ELEMENTS
array.length; // 5
array[9] = 1; // array[5] 到 array[8] 现在都是 holes
// elements 类型: HOLEY_ELEMENTS

V8 之所以有这种区别,它在优化 packed 类型数组的操作上比 holey 类型数组更积极。在 packed 类型数组上大多数操作都可以有效率地执行。相比之下,在 holey 类型数组上,这些操作就需要在原型链上进行额外的检测,并耗费性能高昂的查询。

到目前为止,我们已经看到每种基本 elements 类型(即 Smis,double 和常规 elements 类型)都有两种: packed 版本和 holey 版本。它们不仅可以从 PACKED_SMI_ELEMENTS 转变成 PACKED_DOUBLE_ELEMENTS,而且还可以从任何 PACKED 类型转变成其 HOLEY 对应类型。 总结一下:

  • 最常见的 elements 类型有 PACKED 类型和 HOLEY 类型
  • 在 packed 类型数组上的操作比 holey 类型数组更有效率
  • elements 类型可以从 PACKED 类型转变成 HOLEY 类型

elements 类型 格

这套标记转换系统被 V8 弄成了一个 格。下面是只有几个 elements 类型的简化示意图:

4146bc24269f83e13167d7d74647195e.png

它只能通过格来向下转变。一旦一个 Smis 数组添加了单个浮点数(single floating-point),即便之后使用 Smi 覆盖该浮点数,它都会被标记为 DOUBLE。同样的,一旦一个数组中出现了 hole,即便之后将这个 hole 补上了,它都会被标记为 holey。

V8 目前区分了 21 种不同的 elements 类型,每一种都可能有一堆优化

通常,更特定的 elements 类型支持更细粒度的优化。格中 elements 类型越往下,其对象的操作就会越慢。为了获取最佳性能,避免不太特定的类型的这种不必要的转换,应坚持使用最适合情况的特定 elements 类型。

性能建议

大多数情况下,elements 类型的追踪工作是在底层运行的,没必要考虑得那么细。但为了从系统中获取最大收益,以下几件事情是可以做的。

避免读取超出数组长度的内容

有点出乎意料(鉴于这篇文章的标题)的是,我们的第 1 个性能建议与 elements 类型追踪没有直接联系(尽管背后发生的事情有点像)。读取超过数组长度的数据会对性能产生惊人的影响,例如当 array.length === 5 时去读 array[42] 的数据。这个例子中数组下标 42 已经越界,数组本身就没这属性,JS 引擎就会耗费昂贵的性能去原型链上找。一旦加载遇到这种情况,V8 会记住 "这个加载需要处理特殊情况",而且它的速度再也不会像读取到越界之前那么快了。

不要把循环写成这样:

// 不要这么写!
for (let i = 0, item; (item = items[i]) != null; i++) {doSomething(item);
}

这段代码读取数组中的所有元素,然后再读取一个元素。直到它发现 undefined 的或 null 元素时才结束。(jQuery 在一些地方就这么干的。)

相反,用老方式写循环,并不断迭代,直到到达最后一个元素。

for (let index = 0; index < items.length; index++) {const item = items[index];doSomething(item);
}

如果循环的对象是可迭代的(比如数组和 NodeLists)就更好了,直接用 for-of

for (const item of items) {doSomething(item);
}

对于特定数组,也可以用内置 forEach

items.forEach((item) => {doSomething(item);
});

现在 for-offorEach 的性能都和老式的 for 循环差不多了。

避免读取超出数组长度的内容!在这种情况下,V8 的边界检查会失败,检查该属性是否存在也就会失败,然后 V8 就要从原型链上找了。如果之后在计算中不小心使用到了这个值(也就是超出数组长度的值),影响会更糟,例如:

function Maximum(array) {let max = 0;for (let i = 0; i <= array.length; i++) { // 糟糕的比较if (array[i] > max) max = array[i];}return max;
}

在这里,最后一次迭代超出了数组长度,返回结果为 undefined,这既影响了加载,又影响了比较:不再只比较数字,它要处理特殊情况。把终止条件改为正确的 i < array.length 可使本示例的性能提高 6 倍(在有 10,000 个元素的数组上进行测试,迭代次数只减少了 0.01%)。

避免 elements 类型的变化

通常,如果需要在一个数组上执行很多操作,试着只用一种元素类型,尽可能是特定类型,这样 V8 可以尽可能对这些操作进行优化。

这比看上去要难。比如仅向一个 Smi 数组中添加 -0 就能把它变成 PACKED_DOUBLE_ELEMENTS

const array = [3, 2, 1, +0];
// PACKED_SMI_ELEMENTS
array.push(-0);
// PACKED_DOUBLE_ELEMENTS

结果就是,之后对该数组的任何操作的优化都与对 Smi 的优化不一样。

避免使用 -0,除非明确需要在代码中区分 -0+0。(最好不要这么做)

对于 NaNInfinity 而言都是一样的。它们都被看作是浮点数(doubles),所以在一个 SMI_ELEMENTS 数组中添加一个 NaN 或者是 Infinity,这个数组就会变成 DOUBLE_ELEMENTS

const array = [3, 2, 1];
// PACKED_SMI_ELEMENTS
array.push(NaN, Infinity);
// PACKED_DOUBLE_ELEMENTS

要对一个整数数组进行大量的操作了,在它初始化时就应考虑下把 -0 变成 0NaNInfinity 之类的值就应该过滤掉。这样一来,这个数组才会维持在 PACKED_SMI_ELEMENTS 状态。这种一次性标准化后的开销对于后续优化都是值得的。

实际上,如果要对数字(numbers)数组进行数学操作,可以考虑下 TypedArray。这也有对应的特定的 elements 类型。

优先使用 array 而不是 array-like 的对象

有些 JS 里的对象,特别是 DOM,看起来像是数组但其实它们并不是真正意义上的数组。创建的 array-like 的数组就像下面这样

const arrayLike = {};
arrayLike[0] = 'a';
arrayLike[1] = 'b';
arrayLike[2] = 'c';
arrayLike.length = 3;

这个对象有 length ,也可以通过下标索引访问子元素(就像数组一样!),但它在其原型链上缺少数组方法,比如 forEach。不过仍可以通过下面的方式在这个对象上调用数组的方法

Array.prototype.forEach.call(arrayLike, (value, index) => {console.log(`${ index }: ${ value }`);
});
// 先打印 '0: a', 然后打印 '1: b', 最后打印 '2: c'.

这段代码调用 array-like 对象上内置的 Array.prototype.forEach 方法,结果符合预期。但这比在真数组上调用 forEach 慢,而后者在 V8 中已被高度优化。要多次在此对象上使用内置的数组方法的话,就应先把它转成真数组再用:

const actualArray = Array.prototype.slice.call(arrayLike, 0);
actualArray.forEach((value, index) => {console.log(`${ index }: ${ value }`);
});
// 先打印 '0: a', 然后打印 '1: b', 最后打印 '2: c'.

这种一次性转换的开销对于后续的优化来讲都是值得的,特别是当对数组执行大量操作时。

arguments 对象是一个 array-like 对象,可以在其上调用数组内置函数,但这种操作不会像对真数组那样做全方位的优化。

const logArgs = function() {Array.prototype.forEach.call(arguments, (value, index) => {console.log(`${ index }: ${ value }`);});
};
logArgs('a', 'b', 'c');
// 先打印 '0: a', 然后打印 '1: b', 最后打印 '2: c'.

ES2015 rest 参数可以在这里帮个忙。它们可以用真数组,而不是优雅地用 array-like 的 arguments 对象。

const logArgs = (...args) => {args.forEach((value, index) => {console.log(`${ index }: ${ value }`);});
};
logArgs('a', 'b', 'c');
// 先打印 '0: a', 然后打印 '1: b', 最后打印 '2: c'.

现在你还有啥借口用 arguments 对象。

所以一般来讲,尽可能避免使用 array-like 的对象,应尽可能使用真数组。

避免多态

如果代码中要处理很多不同的 elements 类型的数组,它可能会导致多态操作,这比只用处理单个 elements 类型的代码要慢。

看如下示例,里面调用了各种 elements 类型的库函数。(注意下这不是原来的 Array.prototype.forEach 方法,除了本文讨论的对特定 elements 类型的优化,这个示例自己也有一套优化。)

const each = (array, callback) => {for (let index = 0; index < array.length; ++index) {const item = array[index];callback(item);}
};
const doSomething = (item) => console.log(item);each([], () => {});each(['a', 'b', 'c'], doSomething);
// `PACKED_ELEMENTS` 调用了 `each` 方法。V8 使用了内联缓存
// (或者说叫 "IC") 记住了这个 `each` 方法是被这个 elements 类型调用的。
// 若不出意外,V8 会乐观地假定在 `each` 方法里访问 `array.length` 和 `array[index]` 时
// 是单一的(比如只接受一种 elements 类型),之后每次调用 `each` 方法,V8 就会去检查这个类型
// 是不是 `PACKED_ELEMENTS`,如果是,V8 会重用之前生成的代码;
// 如果不是,就需要做更多事情了each([1.1, 2.2, 3.3], doSomething);
// `PACKED_DOUBLE_ELEMENTS` 调用了 `each` 方法。 V8 此时看到,在它的内联缓存里面,
// 给 `each` 方法传的是不同的 elements 类型的数组了,那么在 `each` 方法里访问 `array.length` 和 `array[index]` 时就被打上了多态的标记。
// 现在每次在调用 `each` 方法时 V8 都要去做下额外的检查:
// 1. 这个是不是 `PACKED_ELEMENTS`(就像上面说过的)
// 2. 这个是不是 `PACKED_DOUBLE_ELEMENTS`
// 3. 这个还是不是其他的 elements 类型
// 这就会引起性能上的损耗each([1, 2, 3], doSomething);
// `PACKED_SMI_ELEMENTS` 调用了 `each` 方法。这就触发了另一个种程度的多态性。现在在内联缓存中,对于 `each` 方法来说有 3 种不同的 elements 类型。从现在开始每次调用 `each` 方法,就需要另外检查 elements 类型,才能将生成的代码重新用于 `PACKED_SMI_ELEMENTS` 数组,而这都需要以消耗性能为代价才能做的。

内置方法(如 Array.prototype.forEach)可以更有效地处理这种多态性,因此如果对性能敏感,请考虑使用这些内置方法而不是用户手写的库函数

V8 中关于单态与多态的另一个例子就跟对象的 shape 相关,也就是对象的隐藏类。要了解更多请参考 这篇文章

避免创建 holes

在真正的代码看来,访问 holey 数组和 packed 数组之间的性能差异通常太小,甚至无法测量。如果性能测试表明在优化的代码中保留每一条机器指令是值得的,那么可以尝试把数组维持在 packed 模式。比如说,我们要创建一个数组

const array = new Array(3);
// 此时这个数组是稀疏的,所以它被标记为 `HOLEY_SMI_ELEMENTS`
// 根据当前的信息这就是最可能的结果array[0] = 'a';
// 等等,这是一个字符而不是一个 Smi,所以 elements 类型转成 `HOLEY_ELEMENTS`array[1] = 'b';
array[2] = 'c';
// 此时,数组的 3 个位置都被填满了。所以数组是 packed 了(不再是稀疏的了)。
// 然而现在已经不能把这个数组再转成一个特定类型比如 `PACKED_ELEMENTS` 了。
// elements 类型仍然为 `HOLEY_ELEMENTS`

一旦数组被标记为 holey,它将永远保持在 holey 状态,即便之后数组里面有元素了

创建数组的更好方法是使用如下方式

const array = ['a', 'b', 'c'];
// elements 类型: PACKED_ELEMENTS

如果事先不知道所有的值,可以创建一个空数组,然后将值 push 进去

const array = [];
// …
array.push(someValue);
// …
array.push(someOtherValue);

这种方法确保了数组永远不会转换为 holey elements 类型。因此,V8 可能会为这个数组的某些操作生成更快的优化代码。

调试 elements 类型

为了弄明白啥是对象的 elements 类型,可用 d8 的调试版本运行(通过在 debug 模式下从源码进行构建,或使用 jsvu 弄到预编译的二进制文件)

out/x64.debug/d8 --allow-natives-syntax

这将打开一个 d8 REPL,其中可用 %DebugPrint(object) 等特殊函数。输出的 elements 字段显示了传递给 这个 debug 函数的对象的 elements 类型。

d8> const array = [1, 2, 3]; %DebugPrint(array);
DebugPrint: 0x1fbbad30fd71: [JSArray]- map = 0x10a6f8a038b1 [FastProperties]- prototype = 0x1212bb687ec1- elements = 0x1fbbad30fd19 <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]- length = 3- properties = 0x219eb0702241 <FixedArray[0]> {#length: 0x219eb0764ac9 <AccessorInfo> (const accessor descriptor)}- elements= 0x1fbbad30fd19 <FixedArray[3]> {0: 11: 22: 3}
[…]

注意,COW 表示copy-on-write,这是另一个内部优化。

在调试构建中可用的另一个有用的 flag 是 --trace-elements-transitions。用上它能让 V8 提示你 elements 类型转换是在啥时发生的。

$ cat my-script.js
const array = [1, 2, 3];
array[3] = 4.56;$ out/x64.debug/d8 --trace-elements-transitions my-script.js
elements transition [PACKED_SMI_ELEMENTS -> PACKED_DOUBLE_ELEMENTS] in ~+34 at x.js:2 for 0x1df87228c911 <JSArray[3]> from 0x1df87228c889 <FixedArray[3]> to 0x1df87228c941 <FixedDoubleArray[22]>
注:该文章翻译自https://v8.dev/blog/elements-kindsV8 的官方博客,这是关于解释在 V8 中「elements」的类型都有哪些的一篇文章,文章有翻译的不是很清楚的地方,欢迎各位指正

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/550473.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IntelliJ IDEA下git版本回退,版本还原

1、选中要回退的文件或者项目 2、复制要回退版本的版本号&#xff1a; 3、然后在branches里check out你想要回退的版本 选择Checkout Tag or Revision… 在弹出的窗口粘贴刚刚复制的版本号&#xff0c;然后点击OK&#xff0c;版本回退成功。

【IDEA】IDEA中使用git将项目上传到码云上

前言 该篇文章记录了使用IDEA上传项目到码云上。 前提是你在 IDEA中集成了git &#xff0c;并且会 git的简单使用 。 一、IDEA上传项目到码云上 1.将项目变成Git能管理的仓库 选中菜单栏 VCS ➡ Import into Version Control ➡ Create GIt Repository…选中当前项目目录&…

activiti 多部门审批_Activiti 基本介绍

简介这两天工作中要用到Activiti&#xff0c;就简单学习了下&#xff0c;做一个记录&#xff0c;好脑子不如烂笔头&#xff0c;记下来牢靠些&#xff0c;来吧&#xff0c;话不多说&#xff0c;一个字&#xff1a;干。Activiti是什么&#xff0c;为什么要用它Activiti项目是一项…

拼接大屏数据展示_可视化大屏的UI设计是根据哪几个方面来进行?

随着大数据产业的发展&#xff0c;越来越多的公司开始意识到数据资源的管理和运用&#xff0c;特别是一些中、大型企业&#xff0c;在日常中会经常用到可视化大屏&#xff0c;这个时候就需要UI设计师能呈现出相应的视觉效果。下面&#xff0c;就给大家介绍一下可视化大屏的UI设…

00600 ora 关闭oracle_Oracle集群高可用故障切换

原文链接[WK-T]ORACLE 10G 配置故障转移(Failover)​blog.itpub.net文章参考&#xff1a;《大话 Oracle RAC 集群 高可用性 备份与恢复》 张晓明 编著Oracle RAC 同时具备HA(High Availiablity) 和LB(LoadBalance). 而其高可用性的基础就是Failover(故障转移). 它指集群中任何一…

如何关闭window10自动更新

如何关闭Windows10的自动更新&#xff1f; 相信很多同学在用Windows10系统的时候&#xff0c;经常跳出更新系统的提示。 有时自动更新的时间&#xff0c;恰好是我们需要急用电脑的时候&#xff0c;而且系统更新比较慢&#xff0c;等待的时间长。 甚至经常会更新失败&#xf…

分割文本_PSENet、PANNet、DBNet三个文本检测算法异同

点击蓝字关注我们这三个文本检测算法都是segment base算法&#xff0c;通过由下而上的方式&#xff0c;先对text进行segment&#xff0c;然后再根据segment text&#xff0c;计算出text的instancePSENet近年来&#xff0c;自然场景文本检测在场景理解、产品识别、自动驾驶和目标…

maven安装过程以及手动添加jar包到本地仓库

Maven安装过程及手动添加JAR包到本地仓库详解 https://blog.csdn.net/niityzu/article/details/50997544 分类&#xff1a; Maven&#xff08;1&#xff09; 版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 一、Maven介绍 Maven是一个项目构建…

怎么把文件放到docker容器里

1、查找所有容器id&#xff1a;docker ps -a 2、查找容器长ID&#xff1a;docker inspect -f {{.ID}} tomcat-container-id 3、拷贝本地文件到容器&#xff1a; 命令&#xff1a;docker cp 本地路径 容器长ID:容器路径 例子&#xff1a;docker cp /home/work/FDFS/1.jpg dfba3…

lisp医院化验系统_医院智能导视系统

众所周知&#xff0c;“看病难”已经成为了全民关注的社会问题&#xff0c;这一问题也不是一朝一夕能解决的。我司研发的医院智能导视系统&#xff0c;避免就医过程中不必要的时间浪费&#xff0c;大大有效的提高就医效率。医院智能导视系统为了有序推进医院信息化工作&#xf…

的环境下 qt 运行在_Ubuntu16.04环境下运行vins mono(环境配置及编译)之ROS kinetic的安装...

所需环境&#xff1a;ubuntu16.04ROS kineticopencv 3.3.1eigen3.3.3ceres solver 1.141.ROS Kinetic 的安装&#xff08;1&#xff09;设置sources.listsudo sh -c echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.l…

db2 jdbc驱动参数_JDBC详细整理(一)

一.什么是JDBCJDBC(Java DataBase Connectivity)就是Java数据库连接&#xff0c;说白了就是用Java语言来操作数据库。原来我们操作数据库是在控制台使用SQL语句来操作数据库&#xff0c;JDBC是用Java语言向数据库发送SQL语句。二.JDBC原理早期SUN公司的天才们想编写一套可以连接…

生物学专业_江南大学微生物学(发酵)20002008历年考研专业课真题汇编

说明 1. 海量考研真题免费发布&#xff0c;欢迎关注公众号『守望考研』&#xff1b;2. 想获取本文对应的PDF文档以便打印使用&#xff0c;欢迎关注公众号了解领取方法&#xff1b;PS: PDF版文档清晰度更高、水印更小南开大学861微生物学1997-2001、2003-2011历年考研专业课真题…

error: ‘XXX‘ is defined but never used (no-unused-vars)报错的解决方案

错误原因 我的项目安装了eslint规范&#xff0c;ESLint 是在 ECMAScript/JavaScript 代码中识别和报告模式匹配的工具&#xff0c;它的目标是保证代码的一致性和避免错误 解决方案 一、在package.json文件内加入如下代码&#xff1a;然后保存重启项目。 "rules":…

平流式初沉池贮砂斗计算_?初沉池、二沉池的作用与区别-亨孚科技

初沉池的主要作用如下:1、去除沉淀物或浮游物&#xff0c;减轻后续处理设施的负荷。使细小的固体凝聚成大粒子&#xff0c;强化固液分离效果。3.它对胶体物质有一定的吸附和去除作用。4、初沉池在一定程度上起调节池塘的作用&#xff0c;对水质发挥一定的均质效应。5.一些废水处…

Navicat连接Oracle数据库失败,提示无效的用户名和密码(Invalid username and password)

1、Navicat是一款非常好用的数据库管理工具&#xff0c;可是一段时间没有使用&#xff0c;突然发现之前建立的Oracle连接无法打开&#xff0c;提示要输入旧密码和新密码以及确认新密码&#xff0c;在Navicat管理工具中连接之前超过180天的Oracle数据库&#xff0c;连接的时候&a…

把关与服务的关系_泉州代做投标书-电子标书值得信赖 - 泉州广告服务

此外&#xff0c;土壤资源对于人们的重要性不言而喻。为了推行土壤环境攻坚治理&#xff0c;江苏省共布设国控点位个&#xff0c;其中&#xff0c;基础点位个&#xff0c;风险点位个&#xff0c;背景点位个&#xff0c;为开展土壤污染调查奠定基础。这些地区监测站点的成功铺设…

口腔取模过程及注意事项_取模变形?教你三种方法,轻松防止取模变形!

点击查看更多精彩内容关键词&#xff1a;取模&#xff1b;适合人群&#xff1a;口腔修复科医生&#xff1b;共1497字 阅读4分钟在牙体修复中&#xff0c;一个完美的修复体是需要一个精确的模型和医生与技师之间的完美配合才能做到的。而因为模型变形出现返工的情况很多&#xf…

思科isis路由的优先级_华为 路由双点双向引入

点击上方蓝字关注我们哈喽&#xff0c;大家好&#xff01;我是艺博东 &#xff0c;是一个思科出身、专注于华为的网工&#xff1b;好了&#xff0c;话不多说&#xff0c;我们直接进入正题。双点双向重发布(OSPF、IS-IS)文章目录一、拓扑二、底层配置三、双点双向一、拓扑二、底…