0

Why the methods from Math is faster than the bitwise operators in JavaScript?

For example:

  1. Math.floor vs | 0 (http://jsperf.com/floor-vs-bitwise22)
  2. Math.max vs a ^ ((a ^ b) & -(a < b)) (http://jsperf.com/max-vs-bitwise-22)
  3. Math.min vs b ^ ((a ^ b) & -(a < b)) (http://jsperf.com/min-vs-bitwise-22)

I want to know at implementation level, because executing something at bit level I thought I would jump all the translations and calls that a computer is gonna do to make what I want, at bit level, which is less CPU operations.

What's happening?

Yves M.
  • 29,855
  • 23
  • 108
  • 144
Acaz Souza
  • 8,311
  • 11
  • 54
  • 97
  • Can you show how you're calculating the timings? – Ash Burlaczenko May 13 '15 at 12:54
  • 3
    That depends on the browser engine... –  May 13 '15 at 12:55
  • Take also a look to https://stackoverflow.com/questions/4614768/javascript-math-ceilmath-abs-optimization – rpadovani May 13 '15 at 12:58
  • 1
    2 and 3 are obviously slower because you're performing much more in _JavaScript_, the question becomes _"Why is `Math.floor` faster than the `|` operator?"_, and comes down to both browser engine and maybe floor saves a little time by not needing to do a int32 conversion – Paul S. May 13 '15 at 13:08
  • 1
    Too many operations. Check this out: http://jsperf.com/max-vs-bitwise-22/2 – slebetman May 13 '15 at 13:09
  • 1
    A simple implementation of Math.max (or Math.min) uses just one comparison to get at the answer. Your code uses 5 calculations. – Hans Kesting May 13 '15 at 13:09
  • Also remember that min/max is not limited to two arguments... –  May 13 '15 at 13:28

2 Answers2

2

… I thought I would jump all the translations and calls that a computer is gonna do …

Modern optimizing JavaScript engines can perform surprising optimizations. In this case, I'd guess that the Math calls simply get inlined.

If you really want to know what's going on, and not just guess, then you'll have to look at the end product of what your optimizer generates. See e.g. How can I see the machine code generated by v8? for details on that.

Community
  • 1
  • 1
MvG
  • 57,380
  • 22
  • 148
  • 276
0

JS numbers are floating-point, except when the JIT optimizer can use an integer type inside a loop or function. As such, instructions like x86 minsd and maxsd can do the job in one single-uop instruction with good throughput and fairly good latency (https://uops.info/). (More efficiently than for integers in general-purpose registers, ironically, although cmp / cmov is fairly efficient there, too and still only 2 uops instead of 1.)

A 2's complement bithack hides the real logic from the JIT optimizer so you're unlikely to have it turn that mess back into x86 cmp / cmov or AArch64 cmp / csel or whatever. (Or in a simple test where the same input is always the max, branchy code-gen can look good, when it would mispredict in real cases. Of course with constant inputs you'd expect things to optimize away after constant-propagation.)


IDK why floor would be faster than | 0. Check the asm to make sure neither optimized away. |0 should be able to use x86 cvttsd2si (convert (with Truncation) scalar double to signed/scalar integer.

You'd expect a JIT to use SSE4.1 roundsd for floor on new-enough x86 CPUs, which is less efficient (2 uops on mainstream Intel CPUs.) Even if the result is used with integer stuff after floor, rounding toward -Infinity is different from rounding toward 0, so it can't just use cvttsd2si. If AVX-512 was available, that could allow a rounding-mode override for vcvtsd2si (use the current rounding mode, or an AVX-512 override) but I doubt that was happening in 2015. Changing the current FP rounding mode for a loop would be possible, but unlikely.

Both are of course no-ops if the value is already an integer.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847