如何编写v8可内联的函数?
是否有工具可以预编译我的代码以静态内联一些函数?静态转换函数和函数调用以避免捕获值?
背景
我注意到我编写的JS程序的瓶颈是一个非常简单的函数调用:我在循环中调用函数,迭代了数百万次,并手动将函数内联(即用其代码替换函数),加速了代码几个数量级。
之后,我试着研究这个问题,但无法推断出v8如何优化函数调用以及如何编写高效的函数的规则。
示例代码:迭代10亿次
incrementing a counter:
let counter = 0; while(counter < 1e9) ++counter;
it takes about ~1 sec, on my system, both on Google Chrome/Chromium and v8. ~14 secs iterating
1e10
times.assigning to the counter the value of an incrementing function:
function incr(c) { return c+1; } let counter = 0; while(counter < 1e9) counter = incr(counter);
it takes about ~1 sec. ~14 secs iterating
1e10
times.calling a function (declared only once) that increments the captured counter:
let counter = 0; function incr() { ++counter; } while(counter < 1e9) incr();
it takes about ~3 sec. ~98 secs iterating
1e10
times.calling a (arrow) function defined in the loop that increments the captured counter:
let counter = 0; while(counter < 1e9) (()=>{ ++counter; })();
it takes about ~24 secs. (I noticed that a named function or an arrow one makes no difference)
calling a (arrow) function defined in the loop to increment the counter without capturing:
let counter = 0; while(counter < 1e9) { const incr = (c)=>c+1; counter = incr(counter); }
it takes about ~22 secs.
我对以下事实感到惊讶:
捕获变量会减慢代码。为什么?这是一般规则吗?在关键性能函数中,我是否应该始终避免捕获变量?
捕获变量的负面影响在迭代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 秒 。 将这些片段放入函数中,声明其他变量或更改某些语句的顺序有时会提高性能,而有时会进一步降低性能。
什么鬼?
我知道有奇怪的效果,比如这个,并且我已经阅读了很多关于如何针对v8优化JS的指南。还是:什么鬼?!
我试着玩了一下导致我开始进行这项研究的JS程序的瓶颈。 我看到了超过4个数量级之间的差异,这些实现我本不希望有任何不同。 我目前相信v8中的数值计算算法的性能是完全不可预测的,并将在C中重新编写瓶颈,并将其公开为v8的函数。