javascript - v8/chrome/node.js函数内联

标签 javascript node.js performance optimization v8

如何编写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/

相关文章:

java - 根据资源包 key 的存在有条件地显示 HTML block

javascript - 如何使 jQuery ui datepicker 按钮图像只读

javascript - 更新/编辑 localStorage - JSONObject

node.js - 使用 Mocha 测试 locomotive.js

c++ - 使用局部变量或多次访问结构值 (C++)

javascript - 使用 javascript/AJAX 将数据插入 MySQL 数据库

node.js - 在不读取整个文件的情况下读取 Node.js 中的第 n 行

javascript - Express.js - 如何使用 res.render 将警报从 Express 发送到客户端?不重发

performance - Spark : Explicit caching can interfere with Catalyst optimizer's ability to optimize some queries?

javascript - 替换浏览器插件中的大量文本