Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WebWorkers execution appears to be much slower than the main thread

I have been working on optimizing some long running JAvaScript, and tried implementing WebWorkers.

I have a collection of independent tasks to compute. In my initial test there are 80 tasks, and on the main thread the complete in 250ms. I thought that I could distribute the tasks across some web workers and get the time down to maybe 50ms.

My data is geometry data structures that nest multiple typed arrays. I have methods that extract all the data to JSON + an array of ArrayBuffer objects, and so I can pass the data as transfered to the WebWorker without duplicating the big arrays.

  • I have tested the data transfer, and confirmed that it is working as expected. My Typed arrays are empty in the main thread after transferring to the WebWorker.
  • I launch (for now) 4 web workers up front, so that when the work is needed to be done, the workers should be ready.
  • As each worker finishes a task, I give it the next in the queue until the queue is empty.
  • I track time in the web worker to see how much is being used doing the actual compute(e.g. ignoring data transfer overhead).
  • I have an 8 core laptop which runs multi-threaded code daily.

Here is my WebWorker script.

importScripts('../lib/MyLib.js');

let timeComputing = 0;  
this.onmessage = function(e) {
  switch (e.data.msg) {
    case 'compute':
        let mesh = ... unpack data;
        let start = performance.now();
        mesh.doexpensiveCompute();
        timeComputing += performance.now() - start;
        ... send data back to the main thread.
        break;
    case 'logTime':
        console.log("timeComputing:" + timeComputing);
  }
}

When the worker logs the time being used, its usually around 130ms per worker, which means the total time is actually almost 500ms. The main thread does all the work in 250ms, so I'm going 100% slower using WebWorkers. For some reason, exactly the same code running in a WebWorker is much much slower than it does on the main thread.

Some of the workloads I have soon might have hundreds of tasks, so I was hoping WebWorkers would keep my page responsive. (currently its not at all on big loads).

Would anyone have any suggestions as to why I am seeing such poor results? Note: I have eliminated the cost of data transfer(which I believe to be minimal) and thread startup here. I am purely measuring the compute time in the worker, which is poor... Does anyone have experience running heavy compute tasks in a webworker?

One idea, is that I my worker script also loads my main engine script. (MyLib.js in the example code), which is a Webpacked script, and quite big. I used this so that hopefully browser caching would mean it doesn't need to request it again. Maybe instead I should generate a minimal version of my engine just for the webworker context.

Thanks for any tips...

like image 741
Philip Taylor Avatar asked Sep 09 '16 20:09

Philip Taylor


People also ask

Are WebWorkers faster?

WebWorkers just run scripts, so they won't be faster than other methods. They shine by running in a different thread and not blocking the UI or any other code that wants to run in the main thread.

How fast is postMessage?

As you might expect, the simple postMessage is fast. It usually takes between 0 to 1 ms to send a message, whether to a web or shared worker.

Where should you place the JavaScript code to run in the context of a web worker?

You can run whatever code you like inside the worker thread, with some exceptions. For example, you can't directly manipulate the DOM from inside a worker, or use some default methods and properties of the window object.

When would you use a web worker?

Web Workers are primarily used for CPU-intensive tasks to be run in the background without any network connectivity required to work on the tasks.


1 Answers

I have now debugged my Worker.

importScripts('../lib/MyLib.js');

Initially, I had thought that re-using my main library js file in the worker would enable the browser to use the cached version of the lib. I.e. the browser would not need to HTTP request the file, or compile it, since it was already in memory. This turns out to be false, and the browser needed to re-request the file and also recompile it.

Because my script is quite large, recompiling became a big overhead as it also seems to need to re-compile it for each thread. I came to this conclusion by measuring the round trip time for each task, while performing zero work in the worker. The round trip time for each thread started very high (300ms), and quickly dropped to < 1ms after a few iterations.

I now use an inline web worker to avoid extra requests and keep my library encapsulated, as described here: http://www.html5rocks.com/en/tutorials/workers/basics/#toc-inlineworkers And also use a cut down script for the worker to a bare minimum.

I now am getting excellent performance. ~50ms for what was 250ms. The first round trip is slow, but not too bad, and inlining the web worker made it a lot faster.

like image 70
Philip Taylor Avatar answered Sep 27 '22 23:09

Philip Taylor