ASM.js benchmark with simple physics

JavaScript performance comparison

Test case created by Jasper

Info

A simple benchmark of asm.js runtime. The setup uses a custom helper object to allocate memory, and the physics is simple, 1D with no acceleration. Basically, just a glorified adder.

NOTE: if your browser doesn't support asm.js (as of today, only firefox nightly does), then this test won't be informative.

Code by Jasper Palfree (wellcaffeinated.net). MIT License.

Preparation code

<script>
var ASM = (function(ASM){

ASM.Types = {
   
    'bool'   : { // uint8
        size: 1,
        pow: 0,
        view: Uint8Array
    },
    'uint8'  : {
        size: 1,
        pow: 0,
        view: Uint8Array
    },
    'int8'   : {
        size: 1,
        pow: 0,
        view: Int8Array
    },
    'uint16' : {
        size: 2,
        pow: 1,
        view: Int16Array
    },
    'int16'  : {
        size: 2,
        pow: 1,
        view: Int16Array
    },
    'uint32' : {
        size: 4,
        pow: 2,
        view: Int32Array
    },
    'uint'   : { // uint32
        size: 4,
        pow: 2,
        view: Uint32Array
    },
    'int32'  : {
        size: 4,
        pow: 2,
        view: Int32Array
    },
    'int'    : { // int32
        size: 4,
        pow: 2,
        view: Int32Array
    },
    'float32': {
        size: 4,
        pow: 2,
        view: Float32Array
    },
    'float'  : { // float32
        size: 4,
        pow: 2,
        view: Float32Array
    },
    'float64': {
        size: 8,
        pow: 3,
        view: Float64Array
    },
    'double' : { // float64
        size: 8,
        pow: 3,
        view: Float64Array
    },
};


var Struct = function Struct( data, ptr, schema, buffer ){

    var key
        ,props
        ;

    this.setPtr( ptr );
    this.buffer = buffer;

    // setup getters/setters
    for (key in schema){

        props = schema[ key ];
        this.addProp( key, props.ptr, props.type );
    }

    // set data
    for (key in data){

        this[ key ] = data[ key ];
    }
};

Struct.prototype = {

    setPtr: function( ptr ){

        this.ptr = ptr;
    },

    addProp: function( name, ptr, type ){

        var self = this
            ,typeProps = ASM.Types[ type ]
            ,viewInst = new typeProps.view( self.buffer )
            ,pow = typeProps.pow
            ;

        self.__defineGetter__(name, function(){
            return viewInst[ (self.ptr + ptr) >> pow ];
        });

        self.__defineSetter__(name, function( val ){
            return viewInst[ (self.ptr + ptr) >> pow ] = val;
        });
    }
};
 
var Collection = function Collection( schema, options ){

    if (!(this instanceof Collection)){

        return new Collection( schema, options );
    }

    options = options || {};
   
    var key
        ,type
        ,size
        ,objSize = 0
        ,tmp
        ,idx
        ,bufferSize
        ,largest = 0
        ,sc = {}
        ,table = [ {},{},{},{} ] // four hashes to guide the order of memory allocation
        ,maxObjects = options.maxObjects || 1000
        ;
   
    for (key in schema){

        type = schema[ key ];
       
        if (typeof type === 'string'){
           
            type = type.toLowerCase();
            tmp = ASM.Types[ type ];
            size = tmp.size;

            if (!size){
                throw 'Type ' + type + ' not supported.';
            }

            // assemble total object size
            objSize += size;
            largest = ( size > largest ) ? size : largest;

            idx = tmp.pow;
            table[ idx ][ key ] = type;
        }
    }

    // round up to the nearest multiple of the largest object size
    objSize = Math.ceil(objSize / largest) * largest;

    this.objSize = objSize;
    this.blockSize = largest;
    // need the largest power of 2 greater than required size
    bufferSize = 2 << Math.floor(Math.log(objSize * maxObjects)/Math.LN2);
    this.buffer = new ArrayBuffer( bufferSize );
    tmp = 0;

    for ( var i = table.length - 1; i >= 0; i-- ){
       
        for (key in table[ i ]){

            !function(key, type, ptr){

                var params = ASM.Types[ type ];

                sc[ key ] = {
                    ptr: ptr,
                    pow: params.pow,
                    size: params.size,
                    type: type
                };

                tmp += params.size;
            }(key, table[ i ][ key ], tmp);
        }
    }

    this._schema = sc;
    this.objs = [];

    // TODO now make members...
};

Collection.prototype = {
    add: function( obj ){

        var ptr = this.objSize * this.objs.length
            inst = new Struct( obj, ptr, this._schema, this.buffer )
            ;

        this.objs.push( inst );
    },
    include: function( fn ){

        var self = this
            ,stdlib = {
                Uint8Array: Uint8Array,
                Int8Array: Int8Array,
                Uint16Array: Uint16Array,
                Int16Array: Int16Array,
                Uint32Array: Uint32Array,
                Int32Array: Int32Array,
                Float32Array: Float32Array,
                Float64Array: Float64Array,
                Math: Math
            }
            ,coln = {
                getLen: function(){
                    return self.objs.length;
                },
                ptr: self.ptr,
                objSize: self.objSize
            }
            ,mixin
            ,key
            ;

        for ( key in self._schema ){

            coln[ '$'+key ] = self._schema[ key ].ptr;
        }

        mixin = fn( stdlib, coln, self.buffer );

        for ( key in mixin ){

            self[ key ] = mixin[ key ];
        }
    }
};

ASM.Collection = Collection;

return ASM;

})(this.ASM || {});

// return test environments with "num" objects in them
function createWorldObjects(num){

var bodies = new ASM.Collection({
    'fixed': 'bool',
    'moi'  : 'float',
    'x'    : 'float',
    'vx'   : 'float'
});

// this "new Function" thing is to get around
// the asm compilation limitation of the firefox nightly
// that only allows one instance of an asm module at a time.
// So we create multiple ones...
bodies.include(new Function('a', 'b', 'c', 'return (' + (function (stdlib, coln, heap){

    var float32 = new stdlib.Float32Array( heap );
    var uint32 = new stdlib.Uint32Array( heap );

    var $x = coln.$x|0;
    var $vx = coln.$vx|0;
    var getLen = coln.getLen;
    var size = coln.objSize|0;
    var iterator = coln.ptr|0;


    function integrate( dt ){
        dt = dt|0;

        var i = 0, l = 0, ptr = 0;
        ptr = iterator|0;
        l = getLen()|0;

        while ((i|0) < (l|0)){
            integrateOne( ptr, dt );
            i = ((i|0) + 1)|0;
            ptr = ((ptr|0) + (size|0))|0;
        }
    }

    function integrateOne( $obj, dt ){
        $obj = $obj|0;
        dt = dt|0;

        var x = 0.0, vx = 0.0;
        x = +float32[($obj + $x) >> 2];
        vx = +float32[($obj + $vx) >> 2];

        float32[($obj + $x) >> 2] = +(+x + +vx * +(dt|0));
    }

    return {
        integrate: integrate
    };
}).toString() + '(a,b,c))'));

// now use asm
var bodiesASM = new ASM.Collection({
    'fixed': 'bool',
    'moi'  : 'float',
    'x'    : 'float',
    'vx'   : 'float'
});

bodiesASM.include(new Function('a', 'b', 'c', 'return (' + (function(stdlib, coln, heap){
   
    var float32 = new stdlib.Float32Array( heap );
    var uint32 = new stdlib.Uint32Array( heap );

    var $x = coln.$x|0;
    var $vx = coln.$vx|0;
    var getLen = coln.getLen;
    var size = coln.objSize|0;
    var iterator = coln.ptr|0;


    function integrate( dt ){
        dt = dt|0;

        var i = 0, l = 0, ptr = 0;
        ptr = iterator|0;
        l = getLen()|0;

        while ((i|0) < (l|0)){
            integrateOne( ptr, dt );
            i = ((i|0) + 1)|0;
            ptr = ((ptr|0) + (size|0))|0;
        }
    }

    function integrateOne( $obj, dt ){
        $obj = $obj|0;
        dt = dt|0;

        var x = 0.0, vx = 0.0;
        x = +float32[($obj + $x) >> 2];
        vx = +float32[($obj + $vx) >> 2];

        float32[($obj + $x) >> 2] = +(+x + +vx * +(dt|0));
    }

    return {
        integrate: integrate
    };
}).toString().replace(/^function\s?\([^)]*\){/, '$& "use asm";') + '(a,b,c))'));
// previous line just inserts "use asm" at the top of the function content

// initialize objects with random positions and velocities
for (var i = 0; i < num; i++){
   var obj = {
      x: Math.random(),
      vx: Math.random()
   };
   bodies.add(obj);
   bodiesASM.add(obj);
}

return {
   bodies: bodies,
   bodiesASM: bodiesASM
};

}

// set up tests
var test10 = createWorldObjects(10);
var test100 = createWorldObjects(100);
var test500 = createWorldObjects(500);
var test1000 = createWorldObjects(1000);
var test10000 = createWorldObjects(10000);


</script>

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
ASM x 10
test10.bodiesASM.integrate( 1 );
pending…
No ASM x 10
test10.bodies.integrate( 1 );
pending…
ASM x 100
test100.bodiesASM.integrate( 1 );
pending…
No ASM x 100
test100.bodies.integrate( 1 );
pending…
ASM x 500
test500.bodiesASM.integrate( 1 );
pending…
No ASM x 500
test500.bodies.integrate( 1 );
pending…
ASM x 1000
test1000.bodiesASM.integrate( 1 );
pending…
No ASM x 1000
test1000.bodies.integrate( 1 );
pending…
ASM x 10000
test10000.bodiesASM.integrate( 1 );
pending…
No ASM x 10000
test10000.bodies.integrate( 1 );
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

0 comments

Add a comment