V8如何进行优化/内联?

18

我想知道是否有可能了解V8如何进行优化和内联的详细信息。

我创建了三个简单的测试函数,它们都计算角度的正弦值。我将它们都放入闭包中,以便V8可以内联局部变量。


1. 使用预先计算好的常数Math.PI / 180,然后执行Math.sin(x * constant)

我使用了这段代码:

var test1 = (function() {
  var constant = Math.PI / 180; // only calculate the constant once

  return function(x) {
    return Math.sin(x * constant);
  };
})();

2. 在程序运行过程中计算常量。

var test2 = (function() {
  var pi = Math.PI; // so that the compiler knows pi cannot change
                    // and it can inline it (Math.PI could change
                    // at any time, but pi cannot)

  return function(x) {
    return Math.sin(x * pi / 180);
  };
})();

3. 使用字面数字并在运行时计算常量。

var test3 = (function() {
  return function(x) {
    return Math.sin(x * 3.141592653589793 / 180);
  };
})();

令人惊讶的是,结果如下:

test1 - 25,090,305 ops/sec
test2 - 16,919,787 ops/sec
test3 - 16,919,787 ops/sec

看起来pi已经在test2中被内联,因为test2test3每秒完成相同数量的操作。

然而,除法似乎没有被优化(即预先计算),因为test1速度显着更快。

  • 如果不手动进行预计算,为什么常数没有被预计算?
  • 是否可能查看V8如何在某个网页上精确优化函数?

我不确定C风格内联的概念是否适用于JIT虚拟机。我只是在推测,但我怀疑V8会对经常调用的函数应用运行时优化,但很难预测。 - mikerobi
1
@mikerobi:这可能是一个天真的问题 - 但是在编译/优化/内联期间,是否有可能像某种调试工具一样直接查看V8正在做什么? - pimvdb
PyPy的JIT例如具有广泛的日志记录,并且他们开始构建一个工具来查看代码经过的所有中间表示形式(Python字节码,JIT IR,机器代码),因此理论上应该可以实现这些功能。但我想这些事情变得更容易是因为(1)实际的JIT是机器生成的,(2)它是跟踪JIT。 - user395760
4
目前V8不执行常量传播。此外,通过在循环中创建函数(setup被多次调用),会对V8造成惩罚:请尝试修改后的测试用例http://jsperf.com/optimizing-v8/2。它消除了由多个闭包的新实例多次重新优化引入的开销,并使结果更加明确。 - Vyacheslav Egorov
我怀疑除了V8开发团队之外的任何人都无法告诉你如何做到这一点。-- 怎么可能?代码是公开的:http://v8.googlecode.com/svn - sehe
显示剩余6条评论
2个回答

7

针对您的第一个问题,我们只能进行有根据的猜测:

严格来说,在第二个和第三个函数中,并不能将pi / 180进行常量折叠,因为你所做的只是将(x * pi)除以180(乘法具有优先级)。

现在,您可能想知道为什么它不改变操作顺序以得到可以进行优化的结果(这个过程称为重组)... 毕竟,结果是等价的(a * b / c = (a * b) / c)。数学是这样规定的,对吗?

然而,数学并不使用浮点数。对于浮点数,情况会更加复杂。 x * pi 可能会四舍五入,那么重新排序将导致不同的结果。这些误差可能非常小,但编译器优化的主要原则是:不要改变程序的结果。在一些不幸的数学基准测试中表现不佳总比在某些图形代码中出现一像素(是的,这可能是可见的)要好。


如果有人对我的函数会产生不同结果的说法表示怀疑,可以试试 x = 0.6784993546113602。我用 Math.random() 找到了这个和许多其他数字。 - user395760
谢谢,这非常有趣。这可以进行更多的研究(例如使用一个常数 (a * b) * x)。 - pimvdb
使用 pi * 180 * x 确实会导致生成不同的结果 - 现在 test1test3 相等,但 test2 变慢了。我认为这可以用运算符优先级来解释。我将尝试搜索 test2pi 不再内联的原因。http://jsperf.com/optimizing-v8-2 - pimvdb
在这种情况下,x * (pi / 180) 的结果是什么?编译器理论上现在可以将 pi / 180 提取出来。 - user578895
@cwolves:当我比较我的初始测试和添加括号的相同测试时,似乎几乎没有任何改进:http://jsperf.com/optimizing-v8/2 和 http://jsperf.com/optimizing-v8/3。这意味着浮点/运算符优先级的想法实际上是*不*适用的。神秘... - pimvdb

4
为了回答你的第二个问题,你可以使用这个工具来查看V8优化你的JS代码后生成的字节码:http://mrale.ph/irhydra/2/。它非常适合在Chrome中对代码进行低级调优。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接