请教一V8上的Nodejs js问题
请教一V8上的Nodejs js问题
function add(x, y) {
return x + y;
}
console.time(“time1”);
for (var i = 0; i < 90000000; ++i) {
add(1, 2);
add(‘a’,‘b’);
}
console.timeEnd(“time1”);
function addText(x, y) {
return x + y;
}
function addNumber(x, y) {
return x + y;
}
console.time(“time2”);
for (var i = 0; i < 90000000; ++i) {
addNumber(1, 2);
addText(‘a’,‘b’);
}
console.timeEnd(“time2”);
这段代码在 V8 上的运行结果为: time1: 1481ms, time2: 102ms。
请教一下具体原因。
在这段代码中,我们定义了两个函数 add
和两个类型特定的函数 addNumber
和 addText
。然后我们分别使用这两个函数进行大量的调用,并测量执行时间。
代码分析
首先,让我们来看一下代码的具体实现:
function add(x, y) {
return x + y;
}
console.time("time1");
for (var i = 0; i < 90000000; ++i) {
add(1, 2);
add('a','b');
}
console.timeEnd("time1");
这里定义了一个通用的 add
函数,可以接受任何类型的参数并尝试相加。这个函数在循环中被调用了 90000000 次,其中一半的调用传入的是数字,另一半传入的是字符串。
接下来是第二个部分:
function addText(x, y) {
return x + y;
}
function addNumber(x, y) {
return x + y;
}
console.time("time2");
for (var i = 0; i < 90000000; ++i) {
addNumber(1, 2);
addText('a','b');
}
console.timeEnd("time2");
这里定义了两个专门用于处理数字和字符串的函数 addNumber
和 addText
。每个函数只处理一种类型的数据。同样地,这两个函数在循环中也被调用了 90000000 次,但这次每个函数每次只处理一种类型的数据。
运行结果
从运行结果来看,time1
的执行时间为 1481ms,而 time2
的执行时间仅为 102ms。这说明了什么?
原因分析
V8 引擎有一个称为“内联缓存”的优化机制。当一个函数被频繁调用时,V8 会尝试优化这个函数的执行速度。在这个例子中:
- 在
time1
中,add
函数需要处理不同类型的数据(数字和字符串),因此每次调用都需要进行类型检查和转换。 - 在
time2
中,addNumber
和addText
函数各自处理一种类型的数据,V8 可以更高效地优化这些特定类型的函数调用。
示例代码解释
-
通用函数:
add
函数可以处理任何类型的输入,但这种灵活性会导致额外的开销,因为引擎需要检查和处理不同类型的数据。 -
类型特定函数:
addNumber
和addText
函数分别处理数字和字符串,这使得 V8 可以更高效地优化这些函数的执行,减少不必要的类型检查和转换。
通过这种方式,V8 能够显著提高特定类型函数的性能,从而导致 time2
的执行时间远低于 time1
。
弱弱的说一句,可能是在addText addNumber裡面省下了很多型別轉換的時間(?)
应该不是没执行循环,两个循环换个顺序 或者单独执行的话,结果任然如此。 应该是类型转换的问题,一个方法的类型判断应该有缓存,如果有种类型传入的话可能会破坏这种缓存,导致多次判断。
是的, 是 V8 内联缓存的问题!
XY 说的?
刚找到了原因,是因为 V8 引擎内联缓存的问题
没错,就是这个原因!
以下是内存和时间的测试结果:
6788KB time运行前使用的内存
time1: 3336ms
9728KB time1 结束后使用的内存
time2: 221ms
9728KB time2 结束后使用的内存
签名: 交流群244728015 《Node.js 服务器框架开发实战》 http://url.cn/Pn07N3
准确来说 是V8内联缓存问题
这个好像在朴灵大大的博客里面有提到!!
能否准确解释一下内联缓存造成这个问题的原因呢?
有链接吗
在这段代码中,add(1, 2)
和 add('a', 'b')
的调用方式导致了不同的执行效率。第一段代码中的 add
函数需要处理不同类型的参数(数字和字符串),这会增加函数调用时的开销。
具体原因:
-
类型检查与转换:
- 在
add(1, 2)
中,两个参数都是数字,因此不会涉及类型检查或转换。 - 在
add('a', 'b')
中,两个参数是字符串,同样不需要类型检查或转换。 - 然而,当
add
接收不同类型的参数时(如一个数字和一个字符串),V8 需要进行类型检查并决定如何处理这两个参数(例如,是否将数字转换为字符串)。这种额外的操作会增加每次函数调用的时间开销。
- 在
-
函数优化:
- V8 引擎会对函数进行优化,尤其是对于频繁调用的函数。如果函数内部操作的对象类型发生变化,V8 可能无法进行高效的内联优化,从而导致性能下降。
-
分支预测:
- 由于每次循环都调用了不同类型的函数(
addNumber
和addText
),V8 引擎可以更好地预测分支并进行优化。相比之下,add
函数内部的操作更加复杂且不固定,使得优化更加困难。
- 由于每次循环都调用了不同类型的函数(
示例代码解释
// 第一段代码
function add(x, y) {
return x + y;
}
console.time("time1");
for (var i = 0; i < 90000000; ++i) {
add(1, 2); // 数字相加
add('a', 'b'); // 字符串拼接
}
console.timeEnd("time1");
// 第二段代码
function addText(x, y) {
return x + y;
}
function addNumber(x, y) {
return x + y;
}
console.time("time2");
for (var i = 0; i < 90000000; ++i) {
addNumber(1, 2); // 数字相加
addText('a', 'b'); // 字符串拼接
}
console.timeEnd("time2");
总结
第一段代码中的 add
函数每次调用都需要处理不同类型的操作(数字相加和字符串拼接),这增加了函数调用的开销。第二段代码通过将不同类型的操作分开到不同的函数中,使 V8 能够更有效地优化这些函数调用。因此,第二段代码的执行时间明显更短。