ParallelJS performance

JavaScript performance comparison

Revision 3 of this test case created

Preparation code

 
<script>
Benchmark.prototype.setup = function() {
    (function () {
        var isNode = typeof module !== 'undefined' && module.exports;
        var setImmediate = setImmediate || function (cb) {
                setTimeout(cb, 0);
        };
        var Worker = isNode ? require(__dirname + '/Worker.js') : self.Worker;
   
        function extend(from, to) {
                if (!to) to = {};
                for (var i in from) {
                        if (to[i] === undefined) to[i] = from[i];
                }
                return to;
        }
   
        function Operation() {
                this._callbacks = [];
                this._errCallbacks = [];
   
                this._resolved = 0;
                this._result = null;
        }
   
        Operation.prototype.resolve = function (err, res) {
                if (!err) {
                        this._resolved = 1;
                        this._result = res;
   
                        for (var i = 0; i < this._callbacks.length; ++i) {
                                this._callbacks[i](res);
                        }
                } else {
                        this._resolved = 2;
                        this._result = err;
   
                        for (var iE = 0; iE < this._errCallbacks.length; ++iE) {
                                this._errCallbacks[iE](res);
                        }
                }
   
                this._callbacks = [];
                this._errCallbacks = [];
        };
   
        Operation.prototype.then = function (cb, errCb) {
                if (this._resolved === 1) { // result
                        if (cb) {
                                cb(this._result);
                        }
   
                        return;
                } else if (this._resolved === 2) { // error
                        if (errCb) {
                                errCb(this._result);
                        }
                        return;
                }
   
                if (cb) {
                        this._callbacks[this._callbacks.length] = cb;
                }
   
                if (errCb) {
                        this._errCallbacks[this._errCallbacks.length] = errCb;
                }
                return this;
        };
   
        var defaults = {
                evalPath: isNode ? __dirname + '/eval.js' : null,
                maxWorkers: isNode ? require('os').cpus().length : 4,
                synchronous: true
        };
   
        function Parallel(data, options) {
                this.data = data;
                this.options = extend(defaults, options);
                this.operation = new Operation();
                this.operation.resolve(null, this.data);
                this.requiredScripts = [];
                this.requiredFunctions = [];
        }
   
        Parallel.prototype.getWorkerSource = function (cb) {
                var preStr = '';
                var i = 0;
                if (!isNode && this.requiredScripts.length !== 0) {
                        preStr += 'importScripts("' + this.requiredScripts.join('","') + '");\r\n';
                }
   
                for (i = 0; i < this.requiredFunctions.length; ++i) {
                        if (this.requiredFunctions[i].name) {
                                preStr += 'var ' + this.requiredFunctions[i].name + ' = ' + this.requiredFunctions[i].fn.toString() + ';';
                        } else {
                                preStr += this.requiredFunctions[i].fn.toString();
                        }
                }
   
                if (isNode) {
                        return preStr + 'process.on("message", function(e) {process.send(JSON.stringify((' + cb.toString() + ')(JSON.parse(e).data)))})';
                } else {
                        return preStr + 'self.onmessage = function(e) {self.postMessage((' + cb.toString() + ')(e.data))}';
                }
        };
   
        Parallel.prototype.require = function () {
                var args = Array.prototype.slice.call(arguments, 0),
                        func;
   
                for (var i = 0; i < args.length; i++) {
                        func = args[i];
   
                        if (typeof func === 'string') {
                                this.requiredScripts.push(func);
                        } else if (typeof func === 'function') {
                                this.requiredFunctions.push({ fn: func })
                        } else if (typeof func === 'object') {
                                this.requiredFunctions.push(func);
                        }
                }
        };
   
        Parallel.prototype._spawnWorker = function (cb) {
                var wrk;
                var src = this.getWorkerSource(cb);
                if (isNode) {
                        wrk = new Worker(this.options.evalPath);
                        wrk.postMessage(src);
                } else {
                        if (Worker === undefined) {
                                return undefined;
                        }
   
                        try {
                                if (this.requiredScripts.length !== 0) {
                                        if (this.options.evalPath !== null) {
                                                wrk = new Worker(this.options.evalPath);
                                                wrk.postMessage(src);
                                        } else {
                                                throw new Error('Can\'t use required scripts without eval.js!');
                                        }
                                } else {
                                        var blob = new Blob([src], { type: 'text/javascript' });
                                        var url = URL.createObjectURL(blob);
   
                                        wrk = new Worker(url);
                                }
                        } catch (e) {
                                if (this.options.evalPath !== null) { // blob/url unsupported, cross-origin error
                                        wrk = new Worker(this.options.evalPath);
                                        wrk.postMessage(src);
                                } else {
                                        throw e;
                                }
                        }
                }
   
                return wrk;
        };
   
        Parallel.prototype.spawn = function (cb) {
                var that = this;
                var newOp = new Operation();
                this.operation.then(function () {
                        var wrk = that._spawnWorker(cb);
                        if (wrk !== undefined) {
                                wrk.onmessage = function (msg) {
                                        wrk.terminate();
                                        that.data = msg.data;
                                        newOp.resolve(null, that.data);
                                };
                                wrk.postMessage(that.data);
                        } else if (that.options.synchronous) {
                                setImmediate(function () {
                                        that.data = cb(that.data);
                                        newOp.resolve(null, that.data);
                                });
                        } else {
                                throw new Error('Workers do not exist and synchronous operation not allowed!');
                        }
                });
                this.operation = newOp;
                return this;
        };
   
        Parallel.prototype._spawnMapWorker = function (i, cb, done) {
                var that = this;
                var wrk = that._spawnWorker(cb);
                if (wrk !== undefined) {
                        wrk.onmessage = function (msg) {
                                wrk.terminate();
                                that.data[i] = msg.data;
                                done();
                        };
                        wrk.postMessage(that.data[i]);
                } else if (that.options.synchronous) {
                        setImmediate(function () {
                                that.data[i] = cb(that.data[i]);
                                done();
                        });
                } else {
                        throw new Error('Workers do not exist and synchronous operation not allowed!');
                }
        };
   
        Parallel.prototype.map = function (cb) {
                if (!this.data.length) {
                        return this.spawn(cb);
                }
   
                var that = this;
                var startedOps = 0;
                var doneOps = 0;
                function done() {
                        if (++doneOps === that.data.length) {
                                newOp.resolve(null, that.data);
                        } else if (startedOps < that.data.length) {
                                that._spawnMapWorker(startedOps++, cb, done);
                        }
                }
   
                var newOp = new Operation();
                this.operation.then(function () {
                        for (; startedOps - doneOps < that.options.maxWorkers && startedOps < that.data.length; ++startedOps) {
                                that._spawnMapWorker(startedOps, cb, done);
                        }
                });
                this.operation = newOp;
                return this;
        };
   
        Parallel.prototype._spawnReduceWorker = function (data, cb, done) {
                var that = this;
                var wrk = that._spawnWorker(cb);
                if (wrk !== undefined) {
                        wrk.onmessage = function (msg) {
                                wrk.terminate();
                                that.data[that.data.length] = msg.data;
                                done();
                        };
                        wrk.postMessage(data);
                } else if (that.options.synchronous) {
                        setImmediate(function () {
                                that.data[that.data.length] = cb(data);
                                done();
                        });
                } else {
                        throw new Error('Workers do not exist and synchronous operation not allowed!');
                }
        };
   
        Parallel.prototype.reduce = function (cb) {
                if (!this.data.length) {
                        throw new Error('Can\'t reduce non-array data');
                }
   
                var runningWorkers = 0;
                var that = this;
                function done(data) {
                        --runningWorkers;
                        if (that.data.length === 1 && runningWorkers === 0) {
                                that.data = that.data[0];
                                newOp.resolve(null, that.data);
                        } else if (that.data.length > 1) {
                                ++runningWorkers;
                                that._spawnReduceWorker([that.data[0], that.data[1]], cb, done);
                                that.data.splice(0, 2);
                        }
                }
   
                var newOp = new Operation();
                this.operation.then(function () {
                        if (that.data.length === 1) {
                                newOp.resolve(null, that.data[0]);
                        } else {
                                for (var i = 0; i < that.options.maxWorkers && i < Math.floor(that.data.length / 2); ++i) {
                                        ++runningWorkers;
                                        that._spawnReduceWorker([that.data[i * 2], that.data[i * 2 + 1]], cb, done);
                                }
   
                                that.data.splice(0, i * 2);
                        }
                });
                this.operation = newOp;
                return this;
        };
   
        Parallel.prototype.then = function (cb, errCb) {
                var that = this;
                var newOp = new Operation();
                this.operation.then(function () {
                        var retData = cb(that.data);
                        if (retData !== undefined) {
                                that.data = retData;
                        }
                        newOp.resolve(null, that.data);
                }, errCb);
                this.operation = newOp;
                return this;
        };
   
        if (isNode) {
                module.exports = Parallel;
        } else {
                self.Parallel = Parallel;
        }
    })();
    var slowSquare = function (n) {
        var i = 0;
        while (++i < n * n) {}
        return i;
    };
};
</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
Parallel
// async test
// Create a job
var p = new Parallel(10000);
p.spawn(slowSquare).then(function() {
  deferred.resolve();
});
pending…
Regular
var p = slowSquare;
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