【发布时间】:2021-05-10 21:28:43
【问题描述】:
做个小检查,看起来既不是V8也不是spidermonkey展开循环,即使很明显,它们是多长时间(字面作为条件,在本地声明):
const f = () => {
let counter = 0;
for (let i = 0; i < 100_000_000; i++) {
counter++;
}
return counter;
};
const g = () => {
let counter = 0;
for (let i = 0; i < 10_000_000; i += 10) {
counter++;
counter++;
counter++;
counter++;
counter++;
counter++;
counter++;
counter++;
counter++;
counter++;
}
return counter;
}
let start = performance.now();
f();
let mid = performance.now();
g();
let end = performance.now();
console.log(
`f took ${(mid - start).toFixed(2)}ms, g took ${(end - mid).toFixed(2)}ms, ` +
`g was ${((mid - start)/(end - mid)).toFixed(2)} times faster.`
);
这有什么原因吗?它们执行相当复杂的优化。标准的for-loops 在 javascript 中是不是很不常见,不值得吗?
编辑:就像一个注释:有人可能会争辩说,也许优化被延迟了。情况似乎并非如此,尽管我不是这里的专家。我使用node --allow-natives-syntax --trace-deopt,手动执行优化,并观察到没有发生反优化(sn-p 用于折叠,实际上不能在浏览器中运行):
const { performance } = require('perf_hooks');
const f = () => {
let counter = 0;
for (let i = 0; i < 100_000_000; i++) {
counter++;
}
return counter;
};
// collect metadata and optimize
f(); f();
%OptimizeFunctionOnNextCall(f);
f();
const start = performance.now();
f();
console.log(performance.now() - start);
普通版和展开版都做,效果一样。
【问题讨论】:
-
呃,你真的希望展开 100000000 次重复的循环吗?没有理智的编译器会生成那么多代码。
-
否,但在某些情况下(例如上面显示的微基准测试),正常的“展开”,例如10 个循环步骤组合在一起,是有益的(请注意,正如公认的答案所提到的,折叠在给出的示例中起作用,但话又说回来,它也是 chrome 上速度差异的 50 倍)。展开不必是全部或全部,在其他语言中也不是这样。你只是想尽可能地避免循环的条件(即使它主要被分支预测器捕获,如果循环体做的很少,它仍然是一个问题)。
标签: javascript v8 spidermonkey loop-unrolling