3

I know Math.abs() and Math.round() are very different functions, but I assumed they would have a relatively similar efficiency.

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 produced these results: round: 136.435ms abs: 4777.983ms

Can anyone explain the radical difference in the timing?

EDIT: When I run the snippit here i get MUCH faster results. Around 2 and 3 ms. Why on earth would it get such radically higher times in a different tab?

Thanks!

Jordan
  • 3,813
  • 4
  • 24
  • 33

2 Answers2

1
  1. I am not sure you are measuring what you think you are

    The random can really mess things up by cache missing. Also you are substracting by 0.5 which is also FPU operation that is quite more complex then abs itself. I am not JAVA coder but I hope Math.abs(x) is floating point and not integer operation (In C/C++ abs is integer and fabs is floating point). I would create array with random numbers set prior to your loops and then use that in your loops

  2. abs

    abs is just sign bit test + non zero mantissa test. if implementation contains brunch then that can seriously slow thing down. Luckily float/double abs implementation does not need any brunch you just mask out the sign bit (as mantissa is not in 2'os complement for standard IEEE 754 formats).

  3. round

    round is test if MSB of fractional part of mantissa is 1 and if yes then is integer increment applied. so the target is bit shifted to integer and MSB fraction bit is extracted. if implementation contains brunch then that can seriously slow thing down but usually faster is extract the MSB fraction bit to Carry flag and use adc. Still this needs more work than abs and so it should be slower.

  4. so why the results are as are?

    Is your implementation/platform using FPU or software emulation? On FPU the complexity of both operations is almost the same (because the overhead of communication with FPU is usually bigger then such operation itself). On emulation it depends on implementation of the operations and target platform architecture (pipelines,cache control,...)

    my guesses are:

    • abs loop is worse with Cache misses
    • abs implementation is not that much optimized as it could be
    • round is optimized by compiler, sometimes round(x)=floor(a+0.5) and you have a-=0.5; before and as you do not use a for anything else there is a possibility compiler ignores the floor(random-0.5+0.5) and use directly floor(random)

[notes]

The times you measured 132ms and 4.7sec are way too big on what HW did you try this? The times from your edit are much more reasonable for common PC HW and code interpreter these days. Did you measure this more then 1 times ?

If you are trying this in brownser then it could be slowed down by anything in its background (like snipped from different page or still downloading something ...) Also OS can pause the execution but not that much

Spektre
  • 49,595
  • 11
  • 110
  • 380
  • Initially, I ran it in Chrome on my PC (not sure on specs, but its fairly powerful), since then, I am just getting the faster speeds. I guess it must have been slowed down by something in the background. – Jordan May 21 '15 at 16:04
1

NOT AN ANSWER, but a bit research.

V8 engine source (https://chromium.googlesource.com/v8/v8.git) I found following implementation in src/math.js

Math.random

function MathRound(x) {
  return %RoundNumber(TO_NUMBER_INLINE(x));
}

Where %RoundNumber should refer to 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 uses V8 enginge and so does Chrome

My testcase:

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

By the V8 source, I would expect to be Math.abs to be faster and it is in Node.JS, but not in Chrome.

Ideas why?

tiblu
  • 2,959
  • 1
  • 22
  • 22
  • have you tried to swap the abs and round loops to check if your measurement is not affected by CACHE ...? also increasing the N=100000 should be safer ... – Spektre May 21 '15 at 08:26
  • @Spektre Swapped around and the results are the same for Node.JS and Chrome. – tiblu May 21 '15 at 08:38
  • by same you mean same as measured before swap or there is no difference between different browsers now? – Spektre May 21 '15 at 10:16
  • @Spektre Same as measured before swap. It would be nice of me to provide graphs and numbers... – tiblu May 21 '15 at 10:42
  • that discard the possibility of measurement affected by differently set CACHE state prior to measurement disadvantaging one of the loops. something like this [Benchmarking affected by VCL](http://stackoverflow.com/q/21516244/2521214) – Spektre May 21 '15 at 11:02
  • @tiblu, thanks for such a detailed response! I think I have a better idea of how they work now. – Jordan May 21 '15 at 16:05