v8 / chrome / node.js函数内联

我如何编写v8将内联的函数?

有没有任何工具来预编译我的代码来静态内联一些函数? 要静态转换函数和函数调用以避免捕获值?


背景

我注意到我写的一个JS程序的瓶颈是一个非常简单的函数调用:我在迭代数百万次的循环中调用函数,并手动内联函数(即用代码replace函数),加快了代码的速度几个数量级。

之后,我试着研究一下这个问题,但是不能推断v8如何优化函数调用的规则,以及如何编写高效的函数。


示例代码:迭代10亿次

  1. 递增计数器:

    let counter = 0; while(counter < 1e9) ++counter; 

    在我的系统上,Google Chrome / Chromium和v8都需要大约1秒的时间。 ~14秒,迭代1e10次。

  2. 给计数器赋值递增函数的值:

     function incr(c) { return c+1; } let counter = 0; while(counter < 1e9) counter = incr(counter); 

    大约需要1秒 。 ~14秒,迭代1e10次。

  3. 调用一个函数(只声明一次),增加捕获的计数器:

     let counter = 0; function incr() { ++counter; } while(counter < 1e9) incr(); 

    大约需要3秒1e10秒迭代1e10次。

  4. 调用循环中定义的(箭头)函数来增加捕获的计数器:

     let counter = 0; while(counter < 1e9) (()=>{ ++counter; })(); 

    大约需要24秒 。 (我注意到一个命名函数或箭头没有区别)

  5. 调用循环中定义的(箭头)函数来递增计数器而不捕获:

     let counter = 0; while(counter < 1e9) { const incr = (c)=>c+1; counter = incr(counter); } 

    大约需要22秒

我很惊讶的事实是:

  • 捕获variables会减慢代码的速度。 为什么? 这是一个普遍的规则? 我应该总是避免在性能关键function中捕获variables吗?

  • 捕获variables的负面影响在迭代1e10倍时增长很多。 那里发生了什么? 如果我不得不采取一个疯狂的猜测,我会说,超过1 ^ 31的variablestypes,并没有为此优化function?

  • 在循环中声明一个函数会减慢代码的速度。 V8并没有优化function呢? 我觉得比这更聪明! 我想我不应该在关键循环中声明函数…

  • 如果在一个循环中声明的函数捕获一个variables,则没有什么区别。 我想捕获一个variables是不利于优化的代码,但没有那么糟糕的不优化?

  • 鉴于所有这一切,我真的很惊讶V8可以完美内联持久的非捕获function。 我想这些是性能明智的唯一可靠的?


编辑1:添加一些额外的片段来暴露额外的怪异。

我创build了一个新的文件,里面有下面的代码:

 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秒

然后我在文件末尾声明了一个新的variables。 任何variables工作正常:只需追加let x; 去那个剪。 代码现在花了12秒完成。

如果不是使用那个incr函数,就像在第一个代码片段中使用++counter一样,额外的variables会使性能从〜1秒降低到〜2.5秒。 把这些片段放到函数中,声明其他variables或者改变某些语句的顺序有时可以提高性能,而其他时间可以进一步降低它的性能。

  • WTF?

  • 我知道这个奇怪的效果,而且我已经阅读了一些关于如何优化JS for v8的指南。 还是:跆拳道?

  • 我玩JS的程序,使我开始这项研究的瓶颈了一下。 我发现实现之间的差异超过了4个数量级,我不希望有任何不同。 我目前确信,v8中的数字运算algorithm的性能是完全不可预知的,我们将重写C中的瓶颈,并将其作为v8的函数公开。

  1. 调用循环中定义的(lambda)函数来增加捕获的计数器
  2. 调用循环中定义的(lambda)函数来增加计数器而不捕获

你为什么认为,创造10亿! 一个循环中的标准函数,可能是个好主意? 特别是如果你只给他们打电话一次(在这个循环内),然后把他们打开。

实际上,我对v8引擎处理这个疯狂任务的效率印象深刻。 我会想,至less要花几分钟才能完成。 再说一遍:我们正在谈论创build10亿个函数,然后再调用一次。

捕获variables的负面影响在迭代1e10倍时增长很多。 那里发生了什么? 如果我不得不采取一个疯狂的猜测,我会说,超过1 ^ 31的variablestypes,并没有为此优化function?

在1 ^ 31之后,它不再是int32了,而是一个64位的浮点数,突然之间,types已经改变了=>代码被优化了。

在循环中声明一个函数会减慢代码的速度。 V8并没有优化function呢? 我觉得比这更聪明! 我想我不应该在关键循环中使用lambdas

在大约100-150次调用之后,function被考虑用于优化。 优化每一个只被调用一次或两次的函数是没有意义的。

如果在一个循环中声明的函数捕获一个variables,则没有什么区别。 我想捕获一个variables是不利于优化的代码,但没有那么糟糕的不优化?

是的,访问一个被捕获的variables需要比访问一个局部variables稍微长一些,但这不是重点。 既不用于优化也不用于非优化的代码。 这里的重点仍然是在循环中创build10亿个函数。

结论:在循环之前创build一次函数然后在循环中调用它。 然后,它不应该有任何显着的性能影响,你通过或捕捉variables。