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

qw3*_*w3n 19 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');
Run Code Online (Sandbox Code Playgroud)

我的结果是

1: 464ms
2: 611ms
Run Code Online (Sandbox Code Playgroud)

我以不同的方式运行了几次,但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');
Run Code Online (Sandbox Code Playgroud)

和我得到的时间

1: 654ms
2: 349ms
Run Code Online (Sandbox Code Playgroud)

小智 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优化.

  • 给读者留言--Dave是Mozilla的JavaScript大师:). (6认同)

kub*_*etz 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;
Run Code Online (Sandbox Code Playgroud)

积分归于Kevin Ballard,他建议Math.round()离开R2().

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

更新:

Firefox的结果与Chrome非常不同.

注意:我在这个领域缺乏经验,所以我猜这里.如果有经验的人可以提供他对这个数字的看法,那将是非常棒的.

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

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

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

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

知道为什么Built-in Math.round这么高效甚至比较静态输入的自定义舍入?