为什么javascript中的Math.round比自定义构建函数慢?

时间:2011-12-13 01:40:10

标签: javascript

我正在搞乱创建一个自定义舍入函数,可以绕到我想要的任何间隔。例如(如果我正在使用度数它会舍入到最接近的15度)无论如何我决定看看它与Math.round相比有多快,并且发现它更慢。我在FF8上使用firebug

function R1(a,b){var c=a%b;return a-c+(c/b+1.5>>1)*b}

function R2(a){return Math.round(a)}

var i,e=1e5;
console.time('1');
i=e;
while(i--){
  R1(3.5,1);
}
console.timeEnd('1');
console.time('2');
i=e;
while(i--){
  R2(3.5);
}
console.timeEnd('2');

我的结果是

1: 464ms
2: 611ms

我以不同的方式运行了几次,但R1总是更快出来。也许这只是一个FF的事情,但如果是这样的话,那是什么原因。

修改 我从函数调用中取出每个函数来查看会发生什么

var i,e=1e5,c;
console.time('1');
i=e;
while(i--){
  c=3.5%1;
  3.5-c+(c/1+1.5>>1)*1;
}
console.timeEnd('1');
console.time('2');
i=e;
while(i--){
  Math.round(3.5);
}
console.timeEnd('2');

和我得到的时间

1: 654ms
2: 349ms

3 个答案:

答案 0 :(得分:15)

简短的回答是,在Firefox 8(但不是9)中,Math.round最终调用了C ++函数,这在JIT中很慢。答案很长,它很复杂,并且在不同版本的Firefox中有所不同。此外,由于涉及JIT,因此在不同的处理器和操作系统上会有所不同。

一些背景知识:根据ECMA-262,Math.round舍入到最接近的整数,除了0.5,它向+ Inf舍入,对于[-0.5,-0.0],它舍入为-0.0(IEEE -754负零)。为了做到这一点,Math.round必须做的不仅仅是R1。它需要对舍入到-0(V8所做的)范围进行一些浮点比较,或者从输入中复制符号(SpiderMonkey会这样做)。

现在,对于Firefox 8,两个循环都由tracejit编译。对于带有R1的循环,R1会内联并编译为纯本机代码。 R2内联并编译为调用名为js_math_round_impl的C ++函数(在js / src / jsmath.cpp中)。

  • 调用任何函数都需要额外费用,因为需要设置参数,调用调用,推送寄存器等等。

  • 调用Math.round等需要额外费用,因为代码需要验证Math.round仍然是默认的Math.round(即,验证没有monkeypatching)。

  • 在JIT中调用C ++函数需要额外费用,因为JIT不知道C ++函数使用了哪些寄存器,因此编译后的JS函数必须在调用之前存储所有调用者保存寄存器,然后重新加载它们。该调用还可以清除其他假设,阻止其他优化。

  • 而且,如前所述,Math.round必须比R1做更多的工作。

我在JS和C中尝试了一些不同的测试,试图弄清楚调用是否更重要,或者-0检查。结果各不相同,但看起来呼叫通常是减速的大部分(70-90%)。

在Firefox 9中,使用JM + TI,R1和R2大致相同。在这种情况下,R1再次内联(我认为)并编译为纯本机代码。对于R2,Math.round由一段jitcode实现,该jitcode直接处理正数,但为负数(和NaN等)调用C ++函数。因此,对于给出的示例,两者都在jitcode中运行,而R2恰好更快一些。

一般来说,像Math.round这样的函数(传统上一直调用C ++函数,但很简单,至少有些情况可以直接在jitcode中完成),性能将依赖很多关于发动机实施者为该特定功能做了多少jitcode优化。

答案 1 :(得分:8)

比较实际上是不正确的。 R2()是一个函数,即调用Math.round()R1()正在直接进行四舍五入。

所以R2包括额外的函数调用 - 这是一个缓慢的操作。

尝试将舍入实现与相同条件进行比较:

function R1(a,b){var c=a%b;return a-c+(c/b+1.5>>1)*b}
R2 = Math.round;

致信 Kevin Ballard ,建议将Math.round()移出R2()

请参阅:http://jsperf.com/comparing-custom-and-bult-in-math-round

更新

Firefox的结果与Chrome非常不同。

注意:我在这方面缺乏经验,所以我猜这里。如果有经验的人可以提供他对这个数字的看法,那就太棒了。

当输入值没有变化时,Firefox看起来很重要。它可以通过这种方式优化R1(3.5),但由于JavaScript的动态特性,优化Math.round可能更难以优化。 Math.round实现可以在代码执行期间的任何时候发生变化。 R1()仅使用算术和按位运算。使用内置Math.roundR2()R3())的函数的性能与其他浏览器(IE 9除外)相同。)

有人有个好主意,并创建了测试用例的第二次修订:

http://jsperf.com/comparing-custom-and-bult-in-math-round/2

此版本还测试了传递给它们的值正在发生变化的函数的性能。

任何想法为什么Built-in Math.round如此高效,甚至与使用静态输入的自定义舍入进行比较?

答案 2 :(得分:0)

我不记得具体来源,但这是一个谷歌技术谈话视频讨论这个。作为对象一部分的字段(例如this.field)比直接引用慢,因为Javascript需要向上移动对象链以查找变量或函数。

编辑:这可能不是这种情况。