Общо показвания

май 03, 2019

JS Compilers

Decided to see how much arithmetic operations can be optimized out by the FE accessible compilers (GCC, Dart).

Assuming we have a list of list of numbers that for a reason unimportant to the presented case we want to represent as arithmetic operations:

var list = [[0, 360/2], [0, 360/3, 360/3*2],... [0,... 180+40+(30*2)]];

On top of that we want to do some more calculations on each of these numbers, lets assume angles and rotate -90 degree, but keep all values positive.

var rotate = (num) => { num < 90 ? num + 270  : num - 90 };
var process = list => list.map(_ => _.map(_ => rotate(_));

Lets also say that we have some code that later on randomly can access any of the values, thus any compiler should:
  1. retain all values
  2. possibly calculate them on compile time*
* because either the code never changes any of the values (i.e.only console.logs them) or because we annotated the code for all items to be immutable and because we are using 'whole app compilers' - this is part of what they do, right, remove unnesessary things. 

At this point I would expect the resulting code to look something like this:

var a = [[270,90],....[270,...]];

This is not the case with either Google Closure compiler nor Dart2JS compiler. 

Interestingly GCC did calculate the initial numbers (before rotation) but only if the result is an integer, if the result was a floating point number it was left as it is (i.e. 360/7 was lest as is). The call to the process method was left intact as well as the logical operand in rotate.

Dart2JS did something similar, but it calculated all initial numbers including the ones resulting in floating point numbers. However calls to both functions was left in the resulting code. 

Still I was sure there has to be a way to tell the compilers that those numbers should all be pre-calculated and  used directly by the VM instead of running calculations on startup. (Yes, I know how fast JS is with simple arithmetic and that several numbers will not slow down the start of the app, but this is just an experiment).

Next step was to try and take out the logical operator from the  rotating function:

var rotate = n => n - 90; 

This will allow negative numbers to go inside the list but for our use case it was okay. Mind you, however that this can significantly change the  behavior in other cases, so it might not be appropriate to do in your case.

Still the result was not what I expected in both GCC and Dart2JS.

I was determined to force the compiler to inline the results... somehow.

Next step was to wrap each number in a direct call to the original rotate function. Yes, no one would write the code like this, but little regexp and voila:

var list = [[rotate(0), rotate(360/2)], .... [.... rotate(180+40+(30*2))]];

Still, no luck in GCC numbers resulting in integers were calculated, but wrapped in the minified identifier for the rotatte function:

var a = [[a(0), a(180)]...];

Same with Dart2JS as before, all numbers calculated before the rotation including floats.

Finally I tried with the simplified (no logical operators) rotate function and GCC gives us this:

var a = [[-90,90],... [...225]];

Note that we still had to compromise with allowing negative values. Of course still preserving the non-integers as original arithmeric expression. It finally hit me - GCC optimizes for size, not for speed or anything else. 360/7 is much shorter than 51.42857142857143

Dart2JS continues to insist that we call a function to make the calculation with minus 90...

We have come a long way in FE with adding compilers and transpilers of all sorts, however we still cannot expect miracles .. or in this case consise understanding of our intent when it comes to little 3d grade arithmetics.

Next step would be to see how does this compile in Rust to target web assembly!