Why is Math.abs taking so much longer than Math.round?
I know that Math.abs () and Math.round () are very different functions, but I assumed they would have relatively similar performance.
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');
The previous ones gave the following results: round: 136.435ms abs: 4777.983ms
Can anyone explain the radical difference in timing?
EDIT: When I run snippit I get A LOT of quick results. About 2 and 3 ms. Why on this card he will receive many more times in another tab?
Thank!
source to share
-
I'm not sure if you are measuring what you consider yourself
Accidental can really mess things up from the cache. Also you subtract
0.5
, which also runs FPU , which is more complicated thanabs
. I am not a JAVA coder, but I hope itMath.abs(x)
is a floating point operation, not an integer (in C / C ++abs
an integer andfabs
is a floating point). I would create an array with random numbers set before your loops and then use that in your loops -
abs
abs
is just a bit bit test + a test without mantissa zero. if the implementation contains a branch, then it can seriously slow things down. Fortunately, the float / double abs implementation doesn't require any branch, you just mask the sign bit (since the mantissa is not part of the 2'os addition for IEEE 754 standard formats). -
round
round
is a test if the MSB of the fractional part of the mantissa is 1, and if so, an integer increment is applied. so the target bit is shifted to an integer and the MSB bit is extracted. if the implementation contains a branch, then this can seriously slow things down, but it is usually faster to extract the MSB bit for the Carry flag and useadc
. However, this requires more work thanabs
that, and therefore it should be slower. -
so why the results are like this:
Is your implementation / platform using FPU or software emulation? In FPU, the complexity of both operations is almost the same (since the communication overhead with the FPU is usually greater than the operation itself) In emulation, it depends on the implementation of the operations and the architecture of the target platform (pipelines, cache management, ...)
my guesses:
- Cycle
-
abs
worse with cache skipping -
abs
the implementation is not as optimized as it might be -
round
is optimized by the compiler, sometimesround(x)=floor(a+0.5)
you havea-=0.5;
before and, since you are not usinga
for anything else, the compiler ignores the possibilitiesfloor(random-0.5+0.5)
and uses directlyfloor(random)
[notes]
The time you measured 132ms and 4.7 seconds is too long for what HW you tried? From time to time, your editing makes much more sense for a regular PC HW and code interpreter these days. Have you measured this more than 1 time?
If you are trying to do this in brownser, then it may be slowed down by something in its background (for example, cut from another page or is still loading something ...) Also the OS may pause execution, but not much
source to share
NO ANSWER, but a little research.
V8 engine source ( https://chromium.googlesource.com/v8/v8.git ) I found the following implementation insrc/math.js
Math.random
function MathRound(x) {
return %RoundNumber(TO_NUMBER_INLINE(x));
}
Where %RoundNumber
to refer tosrc/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 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 uses V8 enginge as well as Chrome
My test file:
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');
}
Results:
- Chrome (42.0.2311.152 m) - Math.random is faster
- Node.JS (v0.10.29) - Math.abs is faster
Thoughts
In the V8 source, I would expect to Math.abs
be faster and it is in Node.JS but not Chrome.
Ideas why?
source to share