我正在搞乱创建一个自定义舍入函数,可以绕到我想要的任何间隔。例如(如果我正在使用度数它会舍入到最接近的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
答案 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.round
(R2()
和R3()
)的函数的性能与其他浏览器(IE 9除外)相同。)
有人有个好主意,并创建了测试用例的第二次修订:
http://jsperf.com/comparing-custom-and-bult-in-math-round/2。
此版本还测试了传递给它们的值正在发生变化的函数的性能。
任何想法为什么Built-in Math.round
如此高效,甚至与使用静态输入的自定义舍入进行比较?
答案 2 :(得分:0)
我不记得具体来源,但这是一个谷歌技术谈话视频讨论这个。作为对象一部分的字段(例如this.field
)比直接引用慢,因为Javascript需要向上移动对象链以查找变量或函数。
编辑:这可能不是这种情况。