Reputation: 81
In order to familiarize myself with web workers I have written a small test where 2 arrays are added element wise. The positions in the target array are distributed to 4 workers. I wanted to measure the performance and experienced a rude awakening.
///init///
const workers = new Array(4), global_elements = 250000;
function createArray(value, elements) {
return new Int8Array(elements).fill(value);
}
let a = createArray(1, global_elements), b = createArray(2,global_elements), c = createArray(0,global_elements), data_recived = 0;
window.URL = window.URL || window.webkitURL;
for(let i=0; i<4; ++i) {
let response = `self.onmessage=function(e){
for(let i=${i*global_elements}; i<${i*global_elements+global_elements}; ++i) {
e.data[2][i] = e.data[0][i] + e.data[1][i];
}
postMessage(0);
}`;
workers[i] = new Worker(URL.createObjectURL(new Blob([response], {type: 'application/javascript'})));
workers[i].onmessage = function(e) {
if(++data_recived === 4) {
t1 = performance.now();
console.log(t1-t0);
}
};
}
///end-init///
//normal
let t0 = performance.now();
for(let i=0; i<global_elements; ++i) {
c[i] = a[i] + b[i];
}
let t1 = performance.now();
console.log(t1-t0);
//worker
t0 = performance.now();
for(let i=0; i<4; ++i) {
workers[i].postMessage([a,b,c]);
}
Sadly, here workers can not even score with increasing global_elements number.
elements: normal | workers
2500: 0.1 | 51.4
25000: 1.5 | 66.5
250000: 4.1 | 182
(I know the performance tests are not optimal.)
Why do my webworkers perform so unexpectedly bad?
Upvotes: 1
Views: 353
Reputation: 81
I've found that the way how to access the arrays in function has a big impact on performance. By saving them locally I was able to reduce calculation time by more then half.
Task: 2DMatrixMul, elements: 250000, worker_count: 8
normal needed: 1010 ms
workers needed: 325 ms
Here is what my little test case ended up with, for those who are interested.
const worker_count = 8, workers = new Array(worker_count), global_elements = 250000;
let data_recived = 0, a = new Tensor(1, [Math.sqrt(global_elements), Math.sqrt(global_elements)]), b = new Tensor(1, [Math.sqrt(global_elements), Math.sqrt(global_elements)]), c = new Tensor(0, [Math.sqrt(global_elements), Math.sqrt(global_elements)]);
window.URL = window.URL || window.webkitURL;
for(let i=0; i<worker_count; ++i) {
let response = `
self.onmessage=function(e){
const a = e.data[0], b = e.data[1], tmp = new Float32Array(${global_elements/worker_count});
let c=${i*global_elements/worker_count}, i=0, z=0, m=0, lim = 0;
for(; c<${i*global_elements/worker_count+global_elements/worker_count}; ++c) {
i = Math.floor(c/${c._shape._dim[0]}), z = c%${c._shape._dim[0]};
for(m=i*${a._shape._dim[1]}, lim = m+${a._shape._dim[1]}; m<lim; ++m) {
tmp[c-${i*global_elements/worker_count}] += a[m] * b[(m - i*${a._shape._dim[1]})*${b._shape._dim[1]}+z];
}
}
postMessage(tmp);
}`;
workers[i] = new Worker(URL.createObjectURL(new Blob([response], {type: 'application/javascript'})));
workers[i].onmessage = function(e) {
pass(c._value, e.data, i);
if(++data_recived === worker_count) {
t1 = performance.now();
console.log(t1-t0);
console.log(c);
}
};
}
function pass(arr, data, index) {
for(let i=index*global_elements/worker_count, len=index*global_elements/worker_count+global_elements/worker_count; i<len; ++i) {
arr[i] = data[i-index*global_elements/worker_count];
}
}
///end-init///
//normal
let t0 = performance.now();
calculations.matmul2D(a,b);
let t1 = performance.now();
console.log(t1-t0);
//worker
t0 = performance.now();
for(let i=0; i<worker_count; ++i) {
workers[i].postMessage([a._value,b._value]);
}
/* calculations.matmul2D( ) looks like this:
static matmul2D(a, b) { //revisite
const row = a._shape._dim[0], column = b._shape._dim[1], a1 = a._shape._dim[1];
let c=0, i=0, m=0, lim=0, len=row * column, result = new Tensor(0, [a._shape._dim[0], column]);
for(; c<len; ++c) {
i = Math.floor(c/row);
for(m=i*a1, lim = m+a1; m<lim; ++m) {
result._value[c] += a._value[m] * b._value[(m - i*a1)*column+(c%row)];
}
}
return result;
}
*/
Upvotes: 2
Reputation: 85012
I suspect the cause of the performance problem is the copying which occurs when sending data to a worker. Memory is not shared between the main thread and the worker, so passing messages usually involves cloning the data you want to send. See more info here: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Transferring_data_to_and_from_workers_further_details
Some browsers support a technique called Transferable Objects, which lets you pass an object without cloning it. You can read more about that here: https://developers.google.com/web/updates/2011/12/Transferable-Objects-Lightning-Fast
Upvotes: 1