Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Math.round in javascript slower than a custom built function?

Tags:

javascript

I was messing around with creating a custom rounding function that could round to whatever interval I wanted. eg(if I was working with degrees it would round to the nearest 15 degrees) Anyways I decided to see how fast it was compared to Math.round and come to find out it is slower. I am using firebug on FF8

function R1(a,b){var c=a%b;return a-c+(c/b+1.5>>1)*b}

function R2(a){return Math.round(a)}

var i,e=1e5;
console.time('1');
i=e;
while(i--){
  R1(3.5,1);
}
console.timeEnd('1');
console.time('2');
i=e;
while(i--){
  R2(3.5);
}
console.timeEnd('2');

and my results were

1: 464ms
2: 611ms

I ran them several times in different ways, but always R1 came out faster. Maybe this is just a FF thing, but if so what is causing it.

EDIT: I took each out of the function call to see what would happen

var i,e=1e5,c;
console.time('1');
i=e;
while(i--){
  c=3.5%1;
  3.5-c+(c/1+1.5>>1)*1;
}
console.timeEnd('1');
console.time('2');
i=e;
while(i--){
  Math.round(3.5);
}
console.timeEnd('2');

and the times I'm getting

1: 654ms
2: 349ms
like image 619
qw3n Avatar asked Dec 13 '11 01:12

qw3n


People also ask

Is math round slow?

round() . R1() is doing rounding directly. So R2 includes extra function call - that is a slow operation.

Does Math round round up or down JavaScript?

Definition and Usage. The Math. round() method rounds a number to the nearest integer. 2.49 will be rounded down (2), and 2.5 will be rounded up (3).

Is math Floor slow?

It suggests that Math. floor is the SLOWEST way to calculate floor in Javascript.

What is math round in JavaScript?

The Math. round() function returns the value of a number rounded to the nearest integer.


2 Answers

The short answer is that in Firefox 8 (but not 9), Math.round ends up calling a C++ function, which is slow in JITs. The long answer is that it's complicated, and it comes out different in different versions of Firefox. Also, because JITs are involved, it's going to be different on different processors and OSs.

A bit of background: According to ECMA-262, Math.round rounds to the nearest integer, except that for 0.5, it rounds toward +Inf, and for [-0.5, -0.0] it rounds to -0.0 (IEEE-754 negative zero). In order to get that right, Math.round has to do more than R1. It will need either to do some floating-point comparisons for the range that rounds to -0 (which V8 does), or copy the sign from the input (which SpiderMonkey does).

Now, for Firefox 8, both loops get compiled by the tracejit. For the loop with R1, R1 gets inlined and compiled to pure native code. R2 is inlined and compiled to call a C++ function called js_math_round_impl (in js/src/jsmath.cpp).

  • Calling any function costs extra because the parameters need to be set up, a call made, registers pushed, etc.

  • Calling Math.round or the like costs extra because the code needs to verify that Math.round is still the default Math.round (i.e., verify no monkeypatching).

  • Calling a C++ function costs extra in JITs because the JIT doesn't know what registers the C++ function uses, so the compiled JS function has to store all caller-save registers before the call and reload them all afterward. The call may also clear out other assumptions, preventing other optimizations.

  • And, as mentioned earlier, Math.round has to do more work than R1.

I tried a few different tests in JS and C to try to figure out whether the call is more important, or the -0 check. The results varied, but it looked like the call was generally the greater part of the slowdown (70-90% of it).

In Firefox 9, with JM+TI, R1 and R2 are about equally fast. In that case, R1 again gets inlined (I think) and compiled to pure native code. For R2, Math.round is implemented by a piece of jitcode that handles positive numbers directly but calls a C++ function for negative numbers (and NaN, etc). So for the example given, both run in jitcode, and R2 happens to be a bit faster.

In general, with functions like Math.round (something that traditionally has been a call to a C++ function, but is simple enough that at least some cases can be done directly in jitcode), the performance is going to depend a lot on how much jitcode optimization the engine implementers did for that particular function.

like image 56
Dave Mandelin Avatar answered Sep 27 '22 16:09

Dave Mandelin


The comparison is actually not correct. R2() is a function, that is calling Math.round(). R1() is doing rounding directly.

So R2 includes extra function call - that is a slow operation.

Try to compare the rounding implementation with same conditions:

function R1(a,b){var c=a%b;return a-c+(c/b+1.5>>1)*b}
R2 = Math.round;

Credits goes to Kevin Ballard who suggested moving Math.round() out of R2().

See: http://jsperf.com/comparing-custom-and-bult-in-math-round .

Update:

Results for Firefox are very different than for Chrome.

Note: I'm inexperienced in this area, so I'm guessing here. If anybody experienced can provide his take on this numbers, that would be awesome.

It looks that Firefox is optimizing heavily when the input value is not changing. It can optimize R1(3.5) this way but optimizing Math.round is probably more difficult to optimize, because of dynamic nature of JavaScript. Math.round implementation can change at any point during the execution of the code. R1() is using only arithmetic and bitwise operations. The performance of functions using built-in Math.round (R2() and R3()) is on par with other browsers (except IE 9 :o) ).

Somebody had a great idea and created 2nd revision of the test-case:

http://jsperf.com/comparing-custom-and-bult-in-math-round/2 .

This revision is testing also performance of functions where the value passed to them is changing.

Any idea why Built-in Math.round so performant compared even to custom rounding with static input?

like image 24
kubetz Avatar answered Sep 27 '22 18:09

kubetz