Skip to content

Instantly share code, notes, and snippets.

@pmav
Created October 17, 2009 16:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pmav/212380 to your computer and use it in GitHub Desktop.
Save pmav/212380 to your computer and use it in GitHub Desktop.
Firefox web workers example
/**
* Firefox Web Workers Example v1.3.1
*
* http://pmav.eu/stuff/javascript-webworkers
*
* This example uses N Web Workers for generating LCG pseudorandom numbers, each Worker starts a new stream.
*
* pmav, 2009-2010
*/
(function () {
MAIN = {
workersNumber : 0, // Total number of Web Workers (dynamic)
workers: [], // Web Workers reference
workersEnded : 0, // Number of terminated Web Workers
lcgNumber : 0, // # of LCG numbers to generate (dynamic)
lcgNumberPower : 0, // LCG numbers in powers of 2
startTime : null, // Current test start time
results : null, // Current test results (for debugging)
testNumber : 1, // Current test number
benchmark : { // Benchmark stuff
mode : false,
currentTest : 0,
currentRun: 0,
runResults : [],
finalResults : [],
tests : [
{
workers: 1,
size: 27, // 2^27
runs: 10
},
{
workers: 2,
size: 27,
runs: 10
},
{
workers: 4,
size: 27,
runs: 10
},
{
workers: 8,
size: 27,
runs: 10
}
]
},
/**
* MAIN.run()
*
* Main code function (this will run only once per test)
*/
run : function(workersNumber, lcgNumberPower) {
var i, t, interval, lcgNumberSeed;
this.workersEnded = 0;
this.workers = [];
this.results = [];
try {
// Get test values
if (typeof workersNumber === "undefined") {
this.workersNumber = parseInt(document.getElementById("webWorkersNumber").innerHTML, 10);
t = parseInt(document.getElementById("lcgNumber").innerHTML, 10);
this.lcgNumber = Math.pow(2, t);
this.lcgNumberPower = t;
} else {
this.workersNumber = workersNumber;
this.lcgNumber = Math.pow(2, lcgNumberPower);
this.lcgNumberPower = lcgNumberPower;
}
interval = Math.ceil(this.lcgNumber / this.workersNumber);
this.startTime = new Date();
// Setup Workers
for (i = 0; i < this.workersNumber; i++) {
this.workers[i] = new Worker(WORKER.scriptFilename);
this.workers[i].onmessage = this.callback;
this.workers[i].onerror = this.error;
}
// Start workers
for (i = 0; i < this.workersNumber; i++) {
lcgNumberSeed = 1 + (interval * i);
if (lcgNumberSeed + interval > this.lcgNumber) {
interval = this.lcgNumber - (interval * i);
}
this.sendMessage(i, lcgNumberSeed, interval);
}
} catch (e) {
alert(e);
}
},
/**
* MAIN.benchmark();
*
* Run several tests and output some useful info
*/
runBenchmark : function () {
if (this.benchmark.currentTest == 0) {
// Benchmark Start
this.benchmark.mode = true;
}
if (this.benchmark.currentRun < this.benchmark.tests[this.benchmark.currentTest].runs) {
// Still have some runs to do
this.run(this.benchmark.tests[this.benchmark.currentTest].workers, this.benchmark.tests[this.benchmark.currentTest].size);
this.benchmark.currentRun++;
} else {
// Next test?
this.benchmark.finalResults.push(UTIL.average(this.benchmark.runResults));
this.benchmark.currentTest++;
// Reset Run
this.benchmark.currentRun = 0;
this.benchmark.runResults = [];
if (this.benchmark.currentTest < this.benchmark.tests.length) {
// Next test!
this.run(this.benchmark.tests[this.benchmark.currentTest].workers, this.benchmark.tests[this.benchmark.currentTest].size);
this.benchmark.currentRun++;
} else {
// Benchmark end
UTIL.log("");
var i, s;
for (i = 0; i < this.benchmark.finalResults.length; i++) {
if (i == 0) {
s = "Standart Test, 100%";
} else {
s = Math.round(((this.benchmark.finalResults[i] / this.benchmark.finalResults[0]) * 100)) + "%";
}
UTIL.log("Benchmark Group #"+(i+1)+" average: "+this.benchmark.finalResults[i]+" ms, "+this.benchmark.tests[i].workers+ " Worker" + ( this.benchmark.tests[i].workers === 1 ? "" : "s" ) + " [" + s + "]");
}
// Reset Test
this.benchmark.currentTest = 0;
this.benchmark.currentRun = 0;
this.benchmark.finalResults = [];
JQ.callback();
}
}
},
/**
* MAIN.sendMessage(int, int, int)
*
* Send a message to a Web Woker with id workerId
*
* workerId - Web Worker unique ID
* lcgNumberSeed - First LCG number (stream seed)
* iterations - # of LCG numbers that each thread will generate
*/
sendMessage : function(workerId, lcgNumberSeed, iterations) {
try {
var data = {};
data.workerId = workerId;
data.payload = {};
data.payload.lcgNumberSeed = lcgNumberSeed;
data.payload.iterations = iterations;
this.workers[workerId].postMessage(JSON.stringify(data));
} catch (e) {
throw e;
}
},
/**
* MAIN.callback(object)
*
* Callback entry point (no context), this function executes in a single thread environment
*/
callback : function(event) {
try {
var data = JSON.parse(event.data);
var workerId = data.workerId;
var payload = data.payload;
MAIN.workers[workerId].terminate();
MAIN.workersEnded += 1;
MAIN.results[workerId] = payload.lastNumber;
if (MAIN.workersEnded === MAIN.workersNumber) { // All Web Workers are done
var time = ((new Date) - MAIN.startTime);
// Output run results
UTIL.log(MAIN.testNumber + ") " + MAIN.workersNumber + " Worker" + ( MAIN.workersNumber === 1 ? "" : "s" ) + ", Test Size: 2^"+MAIN.lcgNumberPower+" - "+ time + " ms");
MAIN.testNumber += 1;
if (MAIN.benchmark.mode) {
MAIN.benchmark.runResults.push(time);
MAIN.runBenchmark();
} else {
JQ.callback();
}
}
} catch (e) {
alert("a: "+e);
}
},
/**
* MAIN.error(object)
*
* Handles worker errors (in a amazing way...)
*/
error : function(error) {
alert("Worker error: " + error.message);
}
}
/**
* WORKER
*
* Web Work related code
*/
WORKER = {
scriptFilename : "assets/js/javascript-webworkers-v1.3.1.js", // Web Worker script
/**
* WORKER.run()
*
* Web Worker main code (thread dies after the execution of this code)
*/
run : function() {
onmessage = function(event) {
var data = JSON.parse(event.data);
var workerId = data.workerId;
var payload = data.payload;
var n, i;
data.workerId = workerId;
data.payload = {};
n = payload.lcgNumberSeed;
for (i = 0; i <= payload.iterations; i += 1) {
n = UTIL.linearCongruentialGenerator(n);
}
data.payload.lastNumber = n; // Only save the last number in the stream
postMessage(JSON.stringify(data)); // Callback to MAIN.callback()
return;
};
},
/**
* WORKER.isWorker()
*
* Check if active thread is a Web Worker
*/
isWorker : function() {
if (typeof document === "undefined") {
return true;
} else {
return false;
}
}
};
/**
* UTIL
*
* Auxiliar code for this example
*/
UTIL = {
a : 1103515245, // LCG a (glibc value)
c : 12345, // LCG c (glibc value)
m : 4294967296, // LCG M (glibc value)
/**
* linearCongruentialGenerator(int)
*
* Linear congruential generator: X(n+1) = ((a * X(n)) + c) % M
* More information: http://en.wikipedia.org/wiki/Linear_congruential_generator
*/
linearCongruentialGenerator : function(n) {
return ((this.a * n) + this.c) % this.m;
},
logDom : null,
log : function(loggingText) {
if (this.logDom === null) {
this.logDom = document.getElementById("log");
}
this.logDom.innerHTML = loggingText + "<br>" + this.logDom.innerHTML;
},
average : function(values) {
var i, sum = 0.0;
for (i = 0; i < values.length; i++) {
sum += values[i];
}
return Math.floor(sum / values.length);
}
}
// Verifies if is a Web Worker thread that is executing at the moment
if (WORKER.isWorker()) {
WORKER.run();
}
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment