Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Strange JS performance issues

I'm having issues with my string distance function working too slowly. I've narrowed it down to the following (the time is for 10K string comparisons):

// desired behavior - 400ms
dp[c + 257] = Math.min(dp[c + 256] + 1, dp[c + 1] + 1, dp[c] + (a[i] != b[j]));

// this is somewhat faster - 300ms
dp[c + 257] = Math.min(dp[c + 256] + 1, dp[c + 1] + 1);
dp[c + 257] = Math.min(dp[c + 257], dp[c] + (a[i] != b[j]));

// even faster - 50ms
dp[c + 257] = Math.min(dp[c + 256] + 1, dp[c + 1] + 1);
dp[c + 257] = Math.min(dp[c + 257], dp[c] + (a[i] != b[j] ? 1 : 0));

So first of all, splitting up a Math.min into two calls is faster than using it once with 3 arguments - how is that possible? Second, why is adding an explicit tertiary expression so much faster than relying on the implicit conversion from bool to int?

Here's a fiddle: https://jsfiddle.net/6bnLvbt6/

like image 614
riv Avatar asked Oct 31 '22 15:10

riv


1 Answers

You've got 3 test cases:

  1. Math.min with 3 arguments with a type cast (bool to int)
  2. 2 Math.min calls with 2 arguments each where the second call has a type cast
  3. 2 Math.min calls with 2 arguments each without the type cast

There is also another difference between 1 and 2&3 where there is an extra + 1 on some of the arguments. I think this is a bug.

The implicit type cast seems to be the most expensive. If you remove the implicit type cast from 1 you get:

dp[c + 257] = Math.min(dp[c + 1], dp[c + 256], dp[c] + (a[i] != b[j] ? 1 : 0));

This takes 250ms (vs 60ms for 3). This still shows that Math.min with 3 arguments is slower. Let's investigate more.

If you simplify the test case to:

// 1
for(var i = 0; i < 10000; i += 1) {
    Math.min(1, 2, 3);
}
// 2
for(var i = 0; i < 10000; i += 1) {
    Math.min(1, 2);
    Math.min(1, 3);
}

The results on my machine are: 1 takes 2500ms, 2 takes 87ms. If you add more arguments to Math.min you'll see that it neatly increases in time by 500ms per extra argument, this reveals something about the implementation.

Browser vendors optimize functions that are used often. If Math.min with more than 2 arguments is uncommon these results are not strange. What you're seeing here is that Math.min has two implementations: one for two arguments, which is really well optimized, and another for more arguments, which isn't optimized. For v8 this is confirmed by apsillers: v8 source on github

like image 70
Halcyon Avatar answered Nov 12 '22 17:11

Halcyon