为什么 Math.abs 比 Math.round 花费的时间长得多?
Why does Math.abs take so much longer than Math.round?
我知道 Math.abs() 和 Math.round() 是非常不同的函数,但我认为它们的效率相对相似。
console.time('round');
for (var i = 0; i < 100000; i++) {
var a = Math.random() - 0.5;
Math.round(a);
}
console.timeEnd('round');
console.time('abs');
for (var i = 0; i < 100000; i++) {
var a = Math.random() - 0.5;
Math.abs(a);
}
console.timeEnd('abs');
之前产生了这些结果:
一轮:136.435ms
绝对值:4777.983 毫秒
谁能解释时间上的根本差异?
编辑:当我 运行 此处的代码片段时,我得到了更快的结果。大约 2 和 3 毫秒。为什么它会在不同的选项卡中获得如此高的时间?
谢谢!
我不确定你衡量的是你认为的自己
随机真的会因为缓存丢失而把事情搞砸。此外,您正在减去 0.5
这也是 FPU 操作,它比 abs
本身复杂得多。我不是 JAVA 编码员,但我希望 Math.abs(x)
是浮点数而不是整数运算(在 C/C++ 中,abs
是整数,fabs
是浮点数) .我会创建一个数组,在你的循环之前设置随机数,然后在你的循环中使用它
abs
abs
只是符号位测试+非零尾数测试。如果实现包含早午餐,那么会严重减慢速度。幸运的是 float/double abs 实现不需要任何早午餐,您只需屏蔽符号位(因为尾数不在标准 IEEE 754 格式的 2'os 补码中)。
round
round
测试尾数的小数部分的 MSB 是否为 1,如果是,则应用整数增量。因此将目标位移为整数并提取 MSB 小数位。如果实现包含早午餐,那么可能会严重减慢速度但通常更快的是将 MSB 小数位提取到进位标志并使用 adc
。这仍然比 abs
需要更多的工作,所以它应该更慢。
为什么结果是这样?
您的 implementation/platform 使用的是 FPU 还是软件仿真?在 FPU 上,两个操作的复杂度几乎相同(因为与 FPU 通信的开销通常比此类操作本身大)。在仿真方面,它取决于操作的实现和目标平台架构(管道、缓存控制……)
我的猜测是:
abs
缓存未命中时循环更糟
abs
实现并没有达到应有的优化程度
round
由编译器优化,有时 round(x)=floor(a+0.5)
并且您之前有 a-=0.5;
并且因为您不将 a
用于其他任何东西所以有一个 possibility 编译器忽略 floor(random-0.5+0.5)
并直接使用 floor(random)
[备注]
您测量的 132 毫秒和 4.7 秒的时间对于您尝试使用的硬件来说太大了?如今,您编辑的时间对于普通 PC 硬件和代码解释器来说要合理得多。您测量了 1 次以上吗?
如果你在 brownser 中尝试这个,那么它可能会被背景中的任何东西减慢速度(比如从不同的页面截取或仍在下载一些东西......)而且 OS 可以暂停执行但不是那个很多
不是答案,而是一些研究。
V8 引擎源代码 (https://chromium.googlesource.com/v8/v8.git) 我在 src/math.js
中找到了以下实现
Math.random
function MathRound(x) {
return %RoundNumber(TO_NUMBER_INLINE(x));
}
其中%RoundNumber
应该是指src/runtime/runtime-maths.cc
RUNTIME_FUNCTION(Runtime_RoundNumber) {
HandleScope scope(isolate);
DCHECK(args.length() == 1);
CONVERT_NUMBER_ARG_HANDLE_CHECKED(input, 0);
isolate->counters()->math_round()->Increment();
if (!input->IsHeapNumber()) {
DCHECK(input->IsSmi());
return *input;
}
Handle<HeapNumber> number = Handle<HeapNumber>::cast(input);
double value = number->value();
int exponent = number->get_exponent();
int sign = number->get_sign();
if (exponent < -1) {
// Number in range ]-0.5..0.5[. These always round to +/-zero.
if (sign) return isolate->heap()->minus_zero_value();
return Smi::FromInt(0);
}
// We compare with kSmiValueSize - 2 because (2^30 - 0.1) has exponent 29 and
// should be rounded to 2^30, which is not smi (for 31-bit smis, similar
// argument holds for 32-bit smis).
if (!sign && exponent < kSmiValueSize - 2) {
return Smi::FromInt(static_cast<int>(value + 0.5));
}
// If the magnitude is big enough, there's no place for fraction part. If we
// try to add 0.5 to this number, 1.0 will be added instead.
if (exponent >= 52) {
return *number;
}
if (sign && value >= -0.5) return isolate->heap()->minus_zero_value();
// Do not call NumberFromDouble() to avoid extra checks.
return *isolate->factory()->NewNumber(Floor(value + 0.5));
}
Math.abs
function MathAbs(x) {
x = +x;
return (x > 0) ? x : 0 - x;
}
Node.JS 使用 V8 enginge Chrome
我的测试用例:
var randoms = [];
for (var i = 0; i < 100000; i++) {
randoms.push(Math.random() - 0.5);
}
for(var r = 0; r < randoms.length; r++) {
console.time('round');
for (var i = 0; i < randoms.length; i++) {
Math.round(randoms[i]);
}
console.timeEnd('round');
console.time('abs');
for (var i = 0; i < randoms.length; i++) {
Math.abs(randoms[i]);
}
console.timeEnd('abs');
}
结果:
- Chrome (42.0.2311.152 m) - Math.random 更快
- Node.JS (v0.10.29) - Math.abs 更快
想法
根据 V8 源代码,我希望 Math.abs
更快,它在 Node.JS 中,但在 Chrome 中没有。
想法为什么?
我知道 Math.abs() 和 Math.round() 是非常不同的函数,但我认为它们的效率相对相似。
console.time('round');
for (var i = 0; i < 100000; i++) {
var a = Math.random() - 0.5;
Math.round(a);
}
console.timeEnd('round');
console.time('abs');
for (var i = 0; i < 100000; i++) {
var a = Math.random() - 0.5;
Math.abs(a);
}
console.timeEnd('abs');
之前产生了这些结果: 一轮:136.435ms 绝对值:4777.983 毫秒
谁能解释时间上的根本差异?
编辑:当我 运行 此处的代码片段时,我得到了更快的结果。大约 2 和 3 毫秒。为什么它会在不同的选项卡中获得如此高的时间?
谢谢!
我不确定你衡量的是你认为的自己
随机真的会因为缓存丢失而把事情搞砸。此外,您正在减去
0.5
这也是 FPU 操作,它比abs
本身复杂得多。我不是 JAVA 编码员,但我希望Math.abs(x)
是浮点数而不是整数运算(在 C/C++ 中,abs
是整数,fabs
是浮点数) .我会创建一个数组,在你的循环之前设置随机数,然后在你的循环中使用它abs
abs
只是符号位测试+非零尾数测试。如果实现包含早午餐,那么会严重减慢速度。幸运的是 float/double abs 实现不需要任何早午餐,您只需屏蔽符号位(因为尾数不在标准 IEEE 754 格式的 2'os 补码中)。round
round
测试尾数的小数部分的 MSB 是否为 1,如果是,则应用整数增量。因此将目标位移为整数并提取 MSB 小数位。如果实现包含早午餐,那么可能会严重减慢速度但通常更快的是将 MSB 小数位提取到进位标志并使用adc
。这仍然比abs
需要更多的工作,所以它应该更慢。为什么结果是这样?
您的 implementation/platform 使用的是 FPU 还是软件仿真?在 FPU 上,两个操作的复杂度几乎相同(因为与 FPU 通信的开销通常比此类操作本身大)。在仿真方面,它取决于操作的实现和目标平台架构(管道、缓存控制……)
我的猜测是:
abs
缓存未命中时循环更糟abs
实现并没有达到应有的优化程度round
由编译器优化,有时round(x)=floor(a+0.5)
并且您之前有a-=0.5;
并且因为您不将a
用于其他任何东西所以有一个 possibility 编译器忽略floor(random-0.5+0.5)
并直接使用floor(random)
[备注]
您测量的 132 毫秒和 4.7 秒的时间对于您尝试使用的硬件来说太大了?如今,您编辑的时间对于普通 PC 硬件和代码解释器来说要合理得多。您测量了 1 次以上吗?
如果你在 brownser 中尝试这个,那么它可能会被背景中的任何东西减慢速度(比如从不同的页面截取或仍在下载一些东西......)而且 OS 可以暂停执行但不是那个很多
不是答案,而是一些研究。
V8 引擎源代码 (https://chromium.googlesource.com/v8/v8.git) 我在 src/math.js
Math.random
function MathRound(x) {
return %RoundNumber(TO_NUMBER_INLINE(x));
}
其中%RoundNumber
应该是指src/runtime/runtime-maths.cc
RUNTIME_FUNCTION(Runtime_RoundNumber) {
HandleScope scope(isolate);
DCHECK(args.length() == 1);
CONVERT_NUMBER_ARG_HANDLE_CHECKED(input, 0);
isolate->counters()->math_round()->Increment();
if (!input->IsHeapNumber()) {
DCHECK(input->IsSmi());
return *input;
}
Handle<HeapNumber> number = Handle<HeapNumber>::cast(input);
double value = number->value();
int exponent = number->get_exponent();
int sign = number->get_sign();
if (exponent < -1) {
// Number in range ]-0.5..0.5[. These always round to +/-zero.
if (sign) return isolate->heap()->minus_zero_value();
return Smi::FromInt(0);
}
// We compare with kSmiValueSize - 2 because (2^30 - 0.1) has exponent 29 and
// should be rounded to 2^30, which is not smi (for 31-bit smis, similar
// argument holds for 32-bit smis).
if (!sign && exponent < kSmiValueSize - 2) {
return Smi::FromInt(static_cast<int>(value + 0.5));
}
// If the magnitude is big enough, there's no place for fraction part. If we
// try to add 0.5 to this number, 1.0 will be added instead.
if (exponent >= 52) {
return *number;
}
if (sign && value >= -0.5) return isolate->heap()->minus_zero_value();
// Do not call NumberFromDouble() to avoid extra checks.
return *isolate->factory()->NewNumber(Floor(value + 0.5));
}
Math.abs
function MathAbs(x) {
x = +x;
return (x > 0) ? x : 0 - x;
}
Node.JS 使用 V8 enginge Chrome
我的测试用例:
var randoms = [];
for (var i = 0; i < 100000; i++) {
randoms.push(Math.random() - 0.5);
}
for(var r = 0; r < randoms.length; r++) {
console.time('round');
for (var i = 0; i < randoms.length; i++) {
Math.round(randoms[i]);
}
console.timeEnd('round');
console.time('abs');
for (var i = 0; i < randoms.length; i++) {
Math.abs(randoms[i]);
}
console.timeEnd('abs');
}
结果:
- Chrome (42.0.2311.152 m) - Math.random 更快
- Node.JS (v0.10.29) - Math.abs 更快
想法
根据 V8 源代码,我希望 Math.abs
更快,它在 Node.JS 中,但在 Chrome 中没有。
想法为什么?