为什么是数学?Round在javascript中比自定义构建函数慢

Why is Math.round in javascript slower than a custom built function?

本文关键字:自定义 构建 函数 javascript Round 为什么      更新时间:2023-09-26

我正在创建一个自定义舍入函数,它可以舍入到我想要的任何间隔。(如果我是用度数来计算的话,它会四舍五入到最接近的15度)无论如何,我决定看看它与数学相比有多快。转了一圈,才发现它比较慢。我在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

简短的回答是,在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或类似的需要额外的费用,因为代码需要验证数学。round仍然是默认的Math.round(即,验证没有monkeypatching)。

  • 调用c++函数在JIT中会花费额外的成本,因为JIT不知道c++函数使用的是什么寄存器,所以编译后的JS函数必须在调用之前存储所有的调用者保存寄存器,然后重新加载它们。该调用还可能清除其他假设,从而阻止其他优化。

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

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

在Firefox 9中,使用JM+TI, R1R2的速度差不多。在这种情况下,R1再次内联(我认为)并编译为纯本地代码。对于R2, Math.round是由一段jitcode实现的,它直接处理正数,但是调用一个c++函数处理负数(以及NaN等)。因此,对于给出的示例,两者都在jitcode中运行,R2碰巧快一点。

一般来说,对于像Math.round这样的函数(传统上是对c++函数的调用,但至少在某些情况下可以直接在jitcode中完成),性能将在很大程度上取决于引擎实现者为该特定函数做了多少jitcode优化。

这个比较实际上是不正确的。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),但优化Math.round可能更难优化,因为JavaScript的动态特性。Math.round的实现可以在代码执行的任何时候改变。R1()只使用算术和位操作。使用内置Math.round (R2()R3())的功能性能与其他浏览器(ie9: 0除外)相当。

有人有了一个好主意,创建了测试用例的第二版:

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

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

你知道为什么Built-in Math.round比静态输入的自定义舍入性能更好吗?

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

编辑:这里的情况可能不是这样。