43

I wrote this:

  var max = 0xffffff * 4;
  var step = 1 / max;
  function cube() {
    var result = 0.;
    for (var x = 0.; x < 1; x += step) {
      result += x * x * x;
    }
    return result;
  }
  function mul() {
    var result = 0.;
    for (var x = 0.; x < 1; x += step) {
      result += x * x;
    }
    return result;
  }
  function go() {
    var r = '';
    r += cube() + ' \n';
    r += mul() + ' \n';
    alert(r);
  }

and see the result in Chrome profiler:

mul: 106ms 
cube: 87ms

How is that possible?

Pavlo
  • 43,301
  • 14
  • 77
  • 113
shal
  • 2,854
  • 4
  • 20
  • 31
  • Yep, throwing this into jsfiddle I see `mul` consistently being 10-20% slower than `cube`. Interesting. – James Thorpe Mar 23 '16 at 12:28
  • 7
    is it also faster when you swap order of calls inside `go()`? – Cee McSharpface Mar 23 '16 at 12:36
  • @dlatikay I tried that, it seemed to make no difference. – James Thorpe Mar 23 '16 at 12:46
  • I was thinking in a direction that `x * x * x` would be converted to and executed as `pow(x, 3)`internally - but [this](http://stackoverflow.com/questions/2940367/what-is-more-efficient-using-pow-to-square-or-just-multiply-it-with-itself) suggests the opposite (but is is C language...) would be interesting how different JS engines behave, did you test with any other than Chrome's? – Cee McSharpface Mar 23 '16 at 12:48
  • 5
    Rather than using Chrome profiler try using the Performance API https://developer.mozilla.org/en-US/docs/Web/API/Performance/now – bhspencer Mar 23 '16 at 13:41
  • 6
    http://jsperf.com/test-math-differenced – epascarello Mar 23 '16 at 13:43
  • 5
    I had the same results, but if I execute first `mul()` and then `cube()` I get `mul()` is faster. Does it happen to anyone else? – Alessandro Mar 23 '16 at 13:46
  • Could it have something to do with the var caching of result? – IanGabes Mar 23 '16 at 13:48
  • Judging from the jsperf test @epascarello put together, it seems Chrome 49 (which I'm running) is indeed faster for `cube` than `mul`, but not 48 or other browsers. – James Thorpe Mar 23 '16 at 13:51
  • http://jsperf.com/cube-vs-square This uses jsPerf's own repetition, and uses constant numbers. Different test, possibly, but shows that simple multiplication or Math.pow() are essentially the same, and multiplying two numbers vs three numbers are not significantly different. Except in IE11, which is, well, IE ;). – Heretic Monkey Mar 23 '16 at 14:00
  • @MikeMcCaughan You might find that the js engines have pretty much optimised away the entirety of your functions there – James Thorpe Mar 23 '16 at 14:02
  • @JamesThorpe Good point. I've updated the perf test. Interestingly, FF is still pretty flat... – Heretic Monkey Mar 23 '16 at 14:13
  • 2
    browsed current V8 source code (the JS engine of Chrome) at https://chromium.googlesource.com/v8/v8.git - findings: a) there is no contraction of `x*x*x` to `pow(x,3)` or `exp(n*log(x))` by interpreter or instruction selectors b) the Float64Mul opcode generation is so sophisticated and decorated with special case handling (+/-0.5, 1.0, ...) and platform/architecture dependent that this Q will be a very hard nut to crack. – Cee McSharpface Mar 23 '16 at 21:16
  • 2
    I tested it on my 8-year-old machine (CPU: Intel Q9550) and `mul` was consistently slower than `cube`, regardless of call sequence. I suspect the optimization is at the CPU level, specifically at how the ALU parallelize operations. The "multiply then add to sum" operation inside `mul` could actually translate into more clock cycles than the "multiply, multiply then add to sum" operation inside `cube`. I think someone with in-depth knowledge about CPUs and assembly instructions may be able to provide a more credible answer. – light Mar 24 '16 at 02:09
  • http://stackoverflow.com/questions/1146455/whats-the-relative-speed-of-floating-point-add-vs-floating-point-multiply @light I do not have much time to spend on this, but your hypothesis made it even more interesting. anyone ready to profile `x * x * x` vs. `x * x * x * x` vs. `x * x * x * x * x` ? – Cee McSharpface Mar 24 '16 at 11:56
  • @dlatikay I just did. On an Intel Q9550 machine, the performance is as expected - from fastest to slowest: `mul`, `cube`, `pow4`, `pow5`. – light Mar 24 '16 at 16:06
  • These are very small numbers, do these results remain like this if you change the starting point (like, not start with 0 but 1, for instance)? – user27221 Mar 25 '16 at 12:24
  • Chrome 49.0.2623.110: `Math.pow(x, 3)` was 64% slower than all three other functions. Test it out on your own browser: http://jsperf.com/square-and-cube – Jamie Apr 03 '16 at 12:01

4 Answers4

35

Your assertion is plain wrong. cube is not faster than mul and your example does not prove it.

In fact, what happens is that the internals of Javascript execution take more time than the actual multiplication, resulting in very similar times for mul and cube. I ran the two functions in a loop, just to increase the difference and the profiler shows 20219 vs 20197, which is insignificant. And BTW, cube is the "slower" one here.

Moreover, this method of profiling doesn't work because both Chrome and Firefox are optimizing a lot before doing maths inside loops. What you think is a loop may very well use a cached value or even a mathematical function that the optimization knows returns the same result.

Here is the code I used:

<script>
 var max = 0xffffff * 4;
  var step = 1 / max;
  function cube() {
    var result = 0.;
    for (var x = 0.; x < 1; x += step) {
      result += x * x * x;
    }
    return result;
  }
  function mul() {
    var result = 0.;
    for (var x = 0.; x < 1; x += step) {
      result += x * x;
    }
    return result;
  }
  function go() {
    var s='';
    for (var i=0; i<100; i++) {
        s+=cube();
        s+=mul();
    }
    console.log(s);
  }
  go();
</script>

Also, as a reference only, watch the video here: https://fosdem.org/2016/schedule/event/mozilla_benchmarking_javascript_tips/ where a Firefox guy explains why microbenchmarking doesn't really mean much.

Siderite Zackwehdex
  • 6,293
  • 3
  • 30
  • 46
  • In Chrome 49, that's not the case in what I'm seeing - `mul` is consistently slower than `cube`. Different results in different browsers - this does seem to be about a specific optimisation Chrome is making - see the comment chain under the Q. Can you show how you constructed your profiling loops? EG Since it's a purely math based function, if you're not using the return values, it might optimise away the entire calculation etc. – James Thorpe Mar 29 '16 at 08:28
  • See [the jsperf test](http://jsperf.com/test-math-differenced) that was posted - Chrome 48 showing the same for both, but 49 and 50 showing more ops per second with `cube` than `mul`. – James Thorpe Mar 29 '16 at 08:30
  • Using or not using the end value, looping 100 times, using Chrome 49.0.2623.87 m. cube consistently but not significantly slower than mul. – Siderite Zackwehdex Mar 29 '16 at 08:34
  • Can you show your code? I think it's only right for an answer on such a question to show exactly how you're profiling it, because everything _I've_ seen so far supports the OP claim in Chrome 49, so right now my _opinion_ (because I have seen nothing to say otherwise) is that _your_ assertions are "plain wrong". – James Thorpe Mar 29 '16 at 08:37
  • Thanks. [That still further matches what I'm seeing](http://imgur.com/NGG3dsa) - more time is spent in `mul` than `cube`. – James Thorpe Mar 29 '16 at 15:06
  • 1
    I can only assume that it is related to your computer somehow. Have you tried swapping the functions around? Maybe it is related to which one is first or something. Anyway, the amount of time difference between the two functions is disproportionately large in your screenshot. – Siderite Zackwehdex Mar 29 '16 at 15:59
  • 1
    Yes - as per the comments under the question, order of functions makes no difference. It really looks like it's a Chrome optimisation on certain CPUs etc. Beginning to think we're going to need a Chrome/V8 engineer to turn up and answer it - that in itself isn't unknown on SO... – James Thorpe Mar 29 '16 at 16:00
  • 2
    Maybe this will help you in your quest: http://stackoverflow.com/questions/18476402/how-to-disable-v8s-optimizing-compiler (How to Disable V8's Optimizing Compiler) – Siderite Zackwehdex Mar 29 '16 at 16:14
  • 2
    I also can confirm @JamesThorpe's findings, cube runs faster in this benchmark as well for my machine – IanGabes Mar 31 '16 at 13:19
0

This is likely because since all your numbers are below 1 the cube function is adding smaller numbers than the square and (I'm not sure if this is actually how it works) therefore taking less time. This is just a guess. And because the numbers are so small this could also be due to inadequate precision. Also I tested with numbers over one the cube is slower with them.

C L K Kissane
  • 34
  • 1
  • 5
  • 2
    Your guess is incorrect, the "size" of a number does not matter whatsoever, the mantissa is always full with numbers, no matter of the "size". Most operations are performed on mantissa. – shal Apr 04 '16 at 06:08
0

maybe the optimizer decides one of them could be executed with vector instructions while the other uses plain old fmul. I'm speculating that 'square' uses fmul and cube uses vector instruction mulpd which can multiply up to 4 doubles in one instruction. I added a 'quad' which did 4 multiplies and its time is pretty close to cube. but when i went to 'five' it slowed down slower than square. That is some indirect evidence that vector instructions are in use for cube and quad.

It would be interesting to see the results on an intel cpu vs arm on a tablet.

dmh2000
  • 683
  • 3
  • 7
-5

On some browsers, javascript starts out as interpreted while the JIT compiles it in the background. Once the javascript is compiled, it starts running faster.

Russell Hankins
  • 1,196
  • 9
  • 17
  • 4
    @Cristik you should double check that assertion ;-) – Didier L Mar 30 '16 at 08:08
  • 2
    @Cristik because nowadays all the most common browsers use JIT compilers for javascript… https://en.wikipedia.org/wiki/List_of_ECMAScript_engines – Didier L Mar 30 '16 at 16:31