Here is a benchmark to compare the asm.js and pure JavaScript implementations of a relatively complex routine:
/*
* PixInsight asm.js benchmark
*/
#define ARRAY_SIZE 1000
#define NUMBER_OF_ITERATIONS 500
#define NUMBER_OF_TESTS 100
var module =
"function MyAsmModule( stdlib )"
+ "\n{"
+ "\n \"use asm\";"
+ "\n"
+ "\n var _sqrt = stdlib.Math.sqrt;"
+ "\n var _sin = stdlib.Math.sin;"
+ "\n var _cos = stdlib.Math.cos;"
+ "\n"
+ "\n function fastSqr( x )"
+ "\n {"
+ "\n x = +x;"
+ "\n return +(x*x);"
+ "\n }"
+ "\n"
+ "\n function fastSqrt( x )"
+ "\n {"
+ "\n x = +x;"
+ "\n return +_sqrt( x );"
+ "\n }"
+ "\n"
+ "\n function fastSin( x )"
+ "\n {"
+ "\n x = +x;"
+ "\n return +_sin( x );"
+ "\n }"
+ "\n"
+ "\n function fastCos( x )"
+ "\n {"
+ "\n x = +x;"
+ "\n return +_cos( x );"
+ "\n }"
+ "\n"
+ "\n function fastTest( x )"
+ "\n {"
+ "\n x = +x;"
+ "\n return +fastSqr( 2.17 * (+fastSqrt( 0.75 * +fastSin( x ) + 0.25 * +fastCos( x ) )) );"
+ "\n }"
+ "\n"
+ "\n return { sqrt: fastSqrt, sin: fastSin, cos: fastCos, test: fastTest };"
+ "\n}"
+ "\n";
//var global = Function( "return this;" )();
//var Asm = MyAsmModule( global );
var Asm = Function( module + "\nreturn MyAsmModule( Function( 'return this;' )() );" )();
function AsmBenchmark( a, b, iterations )
{
var start = Date.now();
for ( var n = 0; n < iterations; ++n )
for ( var i = 0; i < a.length; ++i )
b[i] = Asm.test( a[i] );
return Date.now() - start;
}
function MathBenchmark( a, b, iterations )
{
function sqr( x )
{
return x*x;
}
var start = Date.now();
for ( var n = 0; n < iterations; ++n )
for ( var i = 0; i < a.length; ++i )
b[i] = sqr( 2.17*Math.sqrt( 0.75*Math.sin( a[i] ) + 0.25*Math.cos( a[i] ) ) );
return Date.now() - start;
}
console.show();
console.writeln( "Performing benchmarks. Please wait..." );
console.flush();
var a = new Array( ARRAY_SIZE );
var b = new Array( ARRAY_SIZE );
var c = new Array( ARRAY_SIZE );
var asmTimes = [];
var mathTimes = [];
for ( var n = 0; n < NUMBER_OF_TESTS; ++n )
{
for ( var i = 0; i < ARRAY_SIZE; ++i )
a[i] = Math.random() * 10;
asmTimes.push( AsmBenchmark( a, b, NUMBER_OF_ITERATIONS ) );
mathTimes.push( MathBenchmark( a, c, NUMBER_OF_ITERATIONS ) );
}
function WinsorizedMean( a, k )
{
a.sort( function( a, b ) { return (a < b) ? -1 : ((b < a) ? +1 : 0); } );
var n = Math.trunc( Math.min( k, 0.5 ) * a.length );
for ( var i = 0; i < n; ++i )
{
a[i] = a[n];
a[a.length-i-1] = a[a.length-n-1];
}
return Math.mean( a );
}
console.writeln( format( "asm.js : %4.0f ms", WinsorizedMean( asmTimes, 0.2 ) ) );
console.writeln( format( "Math : %4.0f ms", WinsorizedMean( mathTimes, 0.2 ) ) );
The first thing to point out is that asm.js code has to be implemented as a function, that is, through a string executed as a function body. This is because in the current implementation, an asm.js module can only be compiled once. If we declare the asm.js module and later execute it, this requires at least two compilations, and this is not supported. This limitation will be overcome in a future version of the JavaScript engine.
The second important fact is that an asm.js module needs access to the global JavaScript object. The global object does not exist in my PJSR implementation as a directly accessible object. For example, in a web browser, the global object is typically the 'window' object. There's nothing similar in PJSR. However, this is not a limitation at all, since a reference to the global object can be obtained as follows in any ECMAScript-compliant JavaScript implementation:
var global = Function( "return this;" )();
So the above code uses this 'trick' to pass the global object to the initializer (not to be confused with a constructor) of MyAsmModule.
Now the benchmark results. These are the running times that I have just got from the above script on an Intel Core i7 990X under Fedora 16 Linux x86_64:
asm.js : 78 ms
Math : 67 ms
So the pure JavaScript implementation is clearly faster than the asm.js version. Why? Because the IonMonkey JIT in SpiderMonkey 24 is incredibly efficient. The inner loop code, namely:
b[i] = sqr( 2.17*Math.sqrt( 0.75*Math.sin( a[i] ) + 0.25*Math.cos( a[i] ) ) );
is being translated to machine code so efficiently that it outperforms asm.js. Now if we disable IonMonkey with the following command:
pjsr --ion-jit-
the benchmark result is quite different:
asm.js : 100 ms
Math : 143 ms
(after this test, remember to re-enable the JIT with "pjsr --ion-jit+")
So the bottom line is, in my opinion, that asm.js is probably not worth the effort, in general. If you happen to have a routine that you can implement with the (extremely reduced) asm.js strict subset, and which the JIT is unable to inline efficiently, then you can see some improvements, but this has to be proved on a case by case basis.