如何编写v8将内联的函数?
是否有任何工具可以预编译我的代码以静态内联某些函数?要静态转换函数和函数调用以避免捕获值?
背景
我注意到我编写的JS程序的瓶颈是一个非常简单的函数调用:我在一个循环中调用该函数,迭代了数百万次,然后手动内联该函数(即用其代码替换该函数),从而加快了代码的执行速度几个数量级。
之后,我尝试研究了一下该问题,但是无法推断出有关v8如何优化函数调用以及如何编写有效函数的规则。
示例代码:迭代10亿次
递增计数器:
let counter = 0;
while(counter < 1e9) ++counter;
在我的系统上,无论是在Google Chrome / Chromium还是v8上,大约需要1秒钟。 〜14秒迭代
1e10
次。为计数器分配递增函数的值:
function incr(c) { return c+1; }
let counter = 0;
while(counter < 1e9) counter = incr(counter);
大约需要1秒钟。 〜14秒迭代
1e10
次。调用一个函数(仅声明一次)以增加捕获的计数器:
let counter = 0;
function incr() { ++counter; }
while(counter < 1e9) incr();
大约需要3秒。 〜98秒迭代
1e10
次。调用循环中定义的(箭头)函数来增加捕获的计数器:
let counter = 0;
while(counter < 1e9) (()=>{ ++counter; })();
大约需要24秒。 (我注意到命名函数或箭头没有区别)
调用循环中定义的(箭头)函数以递增计数器而不捕获:
let counter = 0;
while(counter < 1e9) {
const incr = (c)=>c+1;
counter = incr(counter);
}
大约需要22秒。
我对以下事实感到惊讶:
捕获变量会使代码变慢。为什么?这是一般规则吗?我是否应该始终避免在性能关键型函数中捕获变量?
迭代1e10次时,捕获变量的负面影响会大大增加。那里发生了什么事?如果我不得不大胆猜测,我会说超出1 ^ 31的变量会更改类型,而函数没有为此优化吗?
在循环中声明函数会使代码变慢。 v8根本没有优化功能吗?我认为这比这更聪明!我想我永远不要在关键循环中声明函数...
如果在循环中声明的函数是否捕获变量,则差异不大。我想捕获变量对优化代码不利,但对未优化代码不利吗?
考虑到所有这些,我真的很惊讶v8可以完美内联持久的非捕获功能。我猜这些是性能方面唯一可靠的方法?
编辑1:添加一些额外的摘要以暴露出更多的怪异之处。
我创建了一个新文件,其中包含以下代码:
const start = new Date();
function incr(c) { return c+1; }
let counter = 0;
while(counter < 1e9) counter = incr(counter);
console.log( new Date().getTime() - start.getTime() );
它打印接近1秒的值。
然后,我在文件末尾声明了一个新变量。任何变量都可以正常工作:只需将
let x;
附加到该片段上即可。该代码现在花费了大约12秒钟来完成。如果不使用该
incr
函数而是仅使用++counter
作为第一个代码片段,则额外的变量会使性能从〜1秒降低到〜2.5秒。将这些代码片段放入函数中,声明其他变量或更改某些语句的顺序有时可以提高性能,而其他时候则可以进一步降低性能。WTF?
我了解this one之类的怪异效果,并且阅读了许多有关如何针对v8优化JS的指南。还是:WTF ?!
我玩弄了JS程序的瓶颈,这使我开始了这项研究。我发现实现之间的差异超过了4个数量级,这是我所期待的。我目前确信v8中的数字处理算法的性能是完全不可预测的,并且将重写C中的瓶颈并将其作为v8的函数公开。
最佳答案
调用循环中定义的(lambda)函数,以递增捕获的计数器
调用循环中定义的(lambda)函数以递增计数器而不捕获
你为什么认为那创造了十亿!循环使用相同的函数,可能有什么好主意吗?特别是如果您只调用一次它们(在此循环内),然后将它们分开。
实际上,我对v8引擎处理此疯狂任务的效率印象深刻。我本以为,至少要花几分钟才能执行该操作。再说一次:我们正在谈论创建十亿个函数,然后调用它们一次。
迭代1e10次时,捕获变量的负面影响会大大增加。那里发生了什么事?如果我不得不大胆猜测,我会说超出1 ^ 31的变量会更改类型,而函数没有为此优化吗?
正确,超出1 ^ 31的范围不再是int32了,而是您所使用的64位浮点型,并且突然之间,类型已更改=>代码得到了优化。
在循环中声明函数会使代码变慢。 v8根本没有优化功能吗?我认为这比这更聪明!我猜我永远不要在关键循环中使用lambda
大约100-150次调用后,将考虑对函数进行优化。优化仅调用一次或两次的每个最后一个函数是没有意义的。
如果在循环中声明的函数是否捕获变量,则差异不大。我想捕获变量对优化代码不利,但对未优化代码不利吗?
是的,访问捕获的变量比访问局部变量要花一点时间,但这不是重点。既不是针对优化的代码,也不是针对非优化的代码。这里的重点仍然是您在一个循环中创建10亿个函数。
结论:在循环之前创建函数一次,然后在循环中调用它。这样,无论您传递还是捕获变量,它都不会对性能产生重大影响。
关于javascript - v8/chrome/node.js函数内联,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38277737/