Recently I have been trying to use the Web workers interface to experiment with threads in JavaScript.
Trying to make contains with web workers, following these steps:
Here is what I tried:
var MAX_VALUE = 100000000;
var integerArray = Array.from({length: 40000000}, () => Math.floor(Math.random() * MAX_VALUE));
var t0 = performance.now();
console.log(integerArray.includes(1));
var t1 = performance.now();
console.log("Call to doSomething took " + (t1 - t0) + " milliseconds.");
var promises = [];
var chunks = [];
while(integerArray.length) {
chunks.push(integerArray.splice(0,10000000));
}
t0 = performance.now();
chunks.forEach(function(element) {
promises.push(createWorker(element));
});
function createWorker(arrayChunk) {
return new Promise(function(resolve) {
var v = new Worker(getScriptPath(function(){
self.addEventListener('message', function(e) {
var value = e.data.includes(1);
self.postMessage(value);
}, false);
}));
v.postMessage(arrayChunk);
v.onmessage = function(event){
resolve(event.data);
};
});
}
firstTrue(promises).then(function(data) {
// `data` has the results, compute the final solution
var t1 = performance.now();
console.log("Call to doSomething took " + (t1 - t0) + " milliseconds.");
});
function firstTrue(promises) {
const newPromises = promises.map(p => new Promise(
(resolve, reject) => p.then(v => v && resolve(true), reject)
));
newPromises.push(Promise.all(promises).then(() => false));
return Promise.race(newPromises);
}
//As a worker normally take another JavaScript file to execute we convert the function in an URL: http://stackoverflow.com/a/16799132/2576706
function getScriptPath(foo){ return window.URL.createObjectURL(new Blob([foo.toString().match(/^\s*function\s*\(\s*\)\s*\{(([\s\S](?!\}$))*[\s\S])/)[1]],{type:'text/javascript'})); }
Any browser and cpu tried, it is extremely slow compared to just do a simple contains to the initial array.
Why is this so slow? What is wrong with the code above?
References
Edit: The issue is not about .contains() in specific, but it could be other array functions, e.g. .indexOf(), .map(), forEach() etc. Why splitting the work between web workers takes much longer...
This is a bit of a contrived example so it's hard to help optimize for what you're trying to do specifically but one easily-overlooked and fix-able slow path is copying data to the web-worker. If possible you can use ArrayBuffers and SharedArrayBuffers to transfer data to and from web workers quickly.
You can use the second argument to the postMessage function to transfer ownership of an arrayBuffer to the web worker. It's important to note that that buffer will no longer be usable by the main thread until it is transferred back by the web worker. SharedArrayBuffers do not have this limitation and can be read by many workers at once but aren't necessarily supported in all browsers due to a security concern (see mdn for more details)
For example
const arr = new Float64Array(new ArrayBuffer(40000000 * 8));
console.time('posting');
ww.postMessage(arr, [ arr.buffer ]);
console.timeEnd('posting');
takes ~0.1ms to run while
const arr = new Array(40000000).fill(0);
console.time('posting');
ww.postMessage(arr, [ arr ]);
console.timeEnd('posting');
takes ~10000ms to run. This is JUST to transfer the data in the message, not to run the worker logic itself.
You can read more on the postMessage transferList argument here and transferable types here. It's important to note that the way your example is doing a timing comparison includes the web worker creation time, as well, but hopefully this gives a better idea for where a lot of that time is going and how it can be better worked around.