Author Topic: asm.js  (Read 6622 times)

Offline georg.viehoever

  • PTeam Member
  • PixInsight Jedi Master
  • ******
  • Posts: 2132
asm.js
« on: 2013 December 17 04:42:06 »
Juan (or anyone else fluent in Javascript),
could you provide an example of how to use asm.js with images in PI?
Georg
Georg (6 inch Newton, unmodified Canon EOS40D+80D, unguided EQ5 mount)

Offline Andres.Pozo

  • PTeam Member
  • PixInsight Padawan
  • ****
  • Posts: 927
Re: asm.js
« Reply #1 on: 2013 December 20 09:29:50 »
I am trying to do some tests with asm.js but i am stuck in the basics. An asm.js module has to have the syntax:
Code: [Select]
function MyModule(stdlib, foreign, heap)
{
   "use asm";

   var sqrt = stdlib.Math.sqrt;
   ....
}

When creating an instance of MyModule I have to pass a value for the argument stdlib in order to use the function sqrt. I have found examples for Firefox and there they pass a window object. For PixInsight I have no idea of what to do.

A small example of how to use asm.js in PixInsight would be very helpful.

Offline bitli

  • PTeam Member
  • PixInsight Guru
  • ****
  • Posts: 513
Re: asm.js
« Reply #2 on: 2013 December 20 11:54:39 »
Here is some example, just enter it in a file with the script editor and execute it:

Code: [Select]
function DiagModule(stdlib, foreign, heap) {
    "use asm";

    // Variable Declarations
    var sqrt = stdlib.Math.sqrt;

    // Function Declarations
    function square(x) {
        x = +x;
        return +(x*x);
    }

    function diag(x, y) {
        x = +x;
        y = +y;
        // The original example from the standard, DOES NOT WORK
        // return  + sqrt(+square(x) + square(y)) ;
        // This works
        return  + sqrt(+square(x) +  + square(y)) ;
        // The following simple cases also work
        // return + (sqrt(x+y));
        // return + square(x)  ;
    }

    return { diag: diag };
}
Console.show()
Console.writeln(Math);
var fast = DiagModule({Math: Math});     // produces AOT-compiled version
Console.writeln(fast.diag(3, 4));

Note that the example directly cut from the standard does not work. I had to google and hack around. This seems highly experimental and maybe not even designed for human beings.

The example shows how to pass an 'stdlib' (which is just an object containing the reference to the built in).
Have fun !
-- bitli

Offline Juan Conejero

  • PTeam Member
  • PixInsight Jedi Grand Master
  • ********
  • Posts: 7111
    • http://pixinsight.com/
Re: asm.js
« Reply #3 on: 2013 December 20 16:26:43 »
Here is a benchmark to compare the asm.js and pure JavaScript implementations of a relatively complex routine:

Code: [Select]
/*
 * 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:

Code: [Select]
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.
Juan Conejero
PixInsight Development Team
http://pixinsight.com/

Offline bitli

  • PTeam Member
  • PixInsight Guru
  • ****
  • Posts: 513
Re: asm.js
« Reply #4 on: 2013 December 21 01:48:33 »
Hi,
Thanks for the benchmark. By the way I was able to run it without enclosing the function in quotes, which is easier for development.

But I do not think that this benchmark is relevant to how asm.js is designed to be used. The overhead of going from interpreted to compiled code is very high, so it is not designed to optimize micro-functions (a little bit like a GPU cannot easily be used to optimize small functions as the overhead to transfer data and setup the GPU would be too high).

A more realistic test would be to transfer an image to the 'heap' of the module, then make a full operation like deconvolution on the heap data (in asm.js), finally get the image back.  It would be even better to chain some operations on the same image, to avoid the overhead of transferring the image for a single operation. In this case we should gain some time - but only a real test could validate this assumption. This is complicated and preclude the use of most PCL function in the module.

So far I did not find an easy way to transfer data to the heap of the asm module, other than creating a getter/setter and calling it for each element. But I did not spend too much time into it. Anyhow it is delicate and error prone. Trying the manually convert the script 'RepairedHSVCombination' to use asm.js would be an interesting challenge and may accelerate it, but is not for the faint of heart (at least not for me).

IMHO asmjs is not for mere mortals, but for some code generators (see for example  http://mbebenita.github.io/LLJS/) that generate code that can execute fast on a browser or other Javascript host.

For PixInsight I'd rather dream of the integration of the LLVM jit or something similar that would allow to integrate code written in C, Julia or other decent languages.

For Christmas 2014 ?

- bitli

Offline georg.viehoever

  • PTeam Member
  • PixInsight Jedi Master
  • ******
  • Posts: 2132
Re: asm.js
« Reply #5 on: 2013 December 21 02:32:55 »
...A more realistic test would be to transfer an image to the 'heap' of the module, then make a full operation like deconvolution on the heap data (in asm.js), finally get the image back.  It would be even better to chain some operations on the same image, to avoid the overhead of transferring the image for a single operation. In this case we should gain some time - but only a real test could validate this assumption.....
IMHO asmjs is not for mere mortals, but for some code generators (see for example  http://mbebenita.github.io/LLJS/) that generate code that can execute fast on a browser or other Javascript host.
Using it for some image function was what I had in mind for it, but my attempts at getting something working failed.

You are right, asm.js is also intended for code generators. For instance, there are first implementations of using Python in the browser with this strategy ... at this point rather slow and incomplete. But asm.js is definitely heading this way....
Georg
« Last Edit: 2013 December 21 02:40:22 by georg.viehoever »
Georg (6 inch Newton, unmodified Canon EOS40D+80D, unguided EQ5 mount)

Offline Andres.Pozo

  • PTeam Member
  • PixInsight Padawan
  • ****
  • Posts: 927
Re: asm.js
« Reply #6 on: 2013 December 21 03:46:00 »
I have done a test with the inner loop inside the asm.js module. This way I get an speedup although not  too big.
Code: [Select]
function FastDMath(stdlib)
{
   "use asm";

   var cos = stdlib.Math.cos;

   var deg2rad = 0.01745329251994329576923690768489;

   function Cos(x)
   {
      x=+x;
      return +cos(x*deg2rad);
   }

   function Test(n)
   {
      n=+n;
      var sum=0.0;
      var i=0.0;
      for(; i<n; i=i+1.0)
      {
         sum=+(sum+(+Cos(i)));
      }
      return +sum;
   }

   return { cos: Cos, Test: Test };
}

function SlowDMath()
{
   var deg2rad = 0.01745329251994329576923690768489;

   function Cos(x) { return Math.cos(x*deg2rad); }

   function Test(n)
   {
      var sum=0;
      for(var i=0; i<n; i++)
         sum+=Cos(i);
      return sum;
   }
   return { cos: Cos, Test: Test };
}

var fdm=FastDMath( { Math: Math } );

var n=100000000;
var t1=Date.now();
var val_fdm= fdm.Test(n);
var t2=Date.now();
console.writeln(format("asm.js: Val=%f Time=%f", val_fdm, t2-t1));

var sdm=SlowDMath();
var t3=Date.now();
var val_sdm= sdm.Test(n);
var t4=Date.now();
console.writeln(format("Javascript: Val=%f Time=%f", val_sdm, t4-t3));

The result is:
Code: [Select]
run --execute-mode=auto "TestAsm.js"

Processing script file: TestAsm.js
** Warning [344]: TestAsm.js, line 65: successfully compiled asm.js code (total compilation time 0ms)
asm.js: Val=-56.010720 Time=4755.000000
Javascript: Val=-56.010720 Time=6639.000000

It seems that asm.js is still too immature. There are differences between what the compiler validates and what the specification at asmjs.org says it should validate. I.e. I had to implement the "for" loop with a double index variable. I haven't been able to make it to work using an integer index.

I think that more complex modules would get better accelerations but complex modules would need a compiler to generate the asm.js code. However, since PI is going to have an integrated native C++ compiler I don't think asm.js is worth the effort. It is much more interesting to have other capabilities like being able to execute scripts with non modal dialogs. Also having some kind of multithreading would achieve better accelerations than asm.js.