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');
      

Run codeHide result


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!

+3


source to share


2 answers


  • 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 than abs

    . I am not a JAVA coder, but I hope it Math.abs(x)

    is a floating point operation, not an integer (in C / C ++ abs

    an integer and fabs

    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 use adc

    . However, this requires more work than abs

    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, sometimes round(x)=floor(a+0.5)

      you have a-=0.5;

      before and, since you are not using a

      for anything else, the compiler ignores the possibilities floor(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

+1


source


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?

+1


source







All Articles