为什么是数学?Round在javascript中比自定义构建函数慢
Why is Math.round in javascript slower than a custom built function?
我正在创建一个自定义舍入函数,它可以舍入到我想要的任何间隔。(如果我是用度数来计算的话,它会四舍五入到最接近的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, R1
和R2
的速度差不多。在这种情况下,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需要沿着对象链向上查找变量或函数。
编辑:这里的情况可能不是这样。
- 用javascript构建自定义的简单regex
- jQueryUI的自定义构建
- 构建自定义jQuery选择
- 在构建自定义网页刮刀时遇到问题
- 使用jQuery构建可自定义的起始页
- 如何了解自定义构建的dojo的基本版本
- 在Jenkins作业配置页面中,当我的自定义构建步骤添加到作业配置页面时,如何调用JavaScript函数
- 在基于 AngularJS 构建的自定义 UI 中实现自动建议/提前键入
- 如何将自定义缩放控件添加到使用角度谷歌地图构建的地图中
- CKEditor从源代码自定义构建,包括某些票证修复
- 流星:构建自定义表单,提交后需要帮助重定向
- 使用自定义控件构建开放层
- 使用带有 JSPM 的自定义 Kendo-UI 构建
- 在将node.js应用程序部署到Heroku时,如何执行自定义构建脚本
- 如何在创建dojo自定义构建时从层中排除dojo文件
- Angular 2.0-自定义构建/导入并使用“;关于发展;单元
- 为什么是数学?Round在javascript中比自定义构建函数慢
- 进口d3.事件转换为使用rollup的自定义构建
- 使用core-js自定义构建
- AngularJs/ngCordova自定义构建离子注入模块错误