如何编写v8将内联的函数?
是否有任何工具可以预编译我的代码以静态内联某些功能?要静态转换函数和函数调用以避免捕获值?
我注意到我编写的JS程序的瓶颈是一个非常简单的函数调用:我在循环中调用函数迭代数百万次,并且手动内联函数(即用其代码替换函数)加速代码几个数量级。
之后我试着稍微研究一下这个问题,但是无法推断v8如何优化函数调用的规则,以及如何编写有效的函数。
递增一个计数器:
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可以完美地内联持久的非捕获功能。我猜这些是性能方面唯一可靠的吗?
我创建了一个新文件,其中包含以下代码:
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。
答案 0 :(得分:1)
- 调用循环中定义的(lambda)函数,该函数递增捕获的计数器
- 调用循环中定义的(lambda)函数以递增计数器而不捕获
醇>
为什么你认为,创造10亿!!!!!循环中的identic函数,可能有什么好主意?特别是如果你只调用它们一次(在这个循环中),然后将它们移开。
实际上,我对v8引擎处理这个疯狂任务的效率印象深刻。我原以为,执行它至少需要几分钟。再说一遍:我们正在讨论创建10亿个函数,然后再调用它们一次。
捕获变量的负面影响在迭代1e10次时会增长很多。那里发生了什么?如果我不得不采取疯狂的猜测,我会说超过1 ^ 31,变量会改变类型,并且函数没有针对此进行优化?
对,超过1 ^ 31它不再是int32,而是64位浮点数,你正在使用,并且突然之间,类型已经改变了=&gt;代码得到了优化。
在循环中声明一个函数会使代码变慢。 v8根本没有优化功能?我觉得它比那更聪明!我想我永远不应该在关键循环中使用lambdas
在大约100-150次呼叫之后,考虑优化功能。优化每次只调用一次或两次的最后一个函数是没有意义的。
如果在循环中声明的函数捕获变量,那么它没什么区别。我猜捕获一个变量对于优化的代码是不好的,但对于没有优化的代码却不是那么糟糕?
是的,访问捕获的变量所需的时间比访问本地变量要长一点,但这不是重点;既不是优化的也不是非优化的代码。这里的重点仍然是你在一个循环中创建了10亿个函数。
结论:在循环之前创建一次函数,然后在循环中调用它。然后,当你传递或捕获变量时,它不应该有任何显着的性能影响。