0

I've tested web worker messaging in Chrome specifically and getting results of about ~50ms latency to send and receive a message:

// Sender section

let imageData = mCtx.getImageData(0, 0, w, h);
let bitmapData = await createImageBitmap(imageData);
beforeAddBitmapFrame = performance.now();
videoWorker.postMessage({ action : 'addFrameBitmap', data: bitmapData }, [bitmapData]);


// Receiver section

videoWorker.onmessage = function (e) {
    let blob = e.data.data;
    beforeRenderBlobFrame = performance.now();
    let latency = (beforeRenderBlobFrame - beforeAddBitmapFrame); // 50ms
    if(latency > 10) {
       console.log('=== Max Latency Hit ===')
    }
    renderBlobTest(blob);
};

This is basically a loop test where an image is sent to the web worker and the web worker will just send it back to calculate latency. 50 ms here might be nothing at first glace but if you multiply it like for a video with 30 FPS, so doing the math, 50 ms x 30 frames = 1500 ms latency (1.5 seconds) that's a lot considering this is not a network transfer.

What can be done to lower the latency of Web worker messaging?

[UPDATE] To further test I did a simple "ping" test to the web worker at given interval

setInterval(function () {
  let pingTime = new Date().getMilliseconds();
  videoWorker.postMessage({ action: 'ping', pingTime : pingTime });
}, 500);

Then did

if(e.data.pingTime) {
  let pongTime = new Date().getMilliseconds();
  console.log('Got pong: ' + ( pongTime - e.data.pingTime ))
}

Similar result above it averages to ~50ms.

quarks
  • 33,478
  • 73
  • 290
  • 513

1 Answers1

3

You felt in one of the micro-benchmarking traps:

Never run a single instance of the test.

The first run will always be slower, the engine has to warm up, in your case, the whole Worker thread has to be generated and a lot of other stuff have to be initialized (see this Q/A for a list of things delaying the first message).
Also, a single test is prone to report completely false results because of some external and unrelated events (a background app deciding to perform some operations just at that moment, the Garbage Collector kicking in, a UI event, anything...)

const videoWorker = new Worker( generateWorkerURL() );

let startTime;
const latencies = [];
const max_rounds = 10;

// Receiver section
videoWorker.onmessage = function (e) {
  const endTime = performance.now();
  e.data.close();
  const latency = (endTime - startTime);
  // store the current latency
  latencies.push( latency );
  if( latencies.length < max_rounds ) {
    performTest();
  }
  else {
    logResults();
  }
};
// initial call
performTest();

// the actual test code
async function performTest() {
  // we'll build a new random image every test
  const w = 1920;
  const h = 1080;
  // make some noise
  const data = Uint32Array.from( { length: w * h }, ()=> Math.random * 0xFFFFFF + 0xFF000000);
  const imageData = new ImageData( new Uint8ClampedArray( data.buffer ), w, h );
  let bitmapData = await createImageBitmap(imageData);
  // start measuring the time it takes to transfer
  startTime = performance.now();
  videoWorker.postMessage( bitmapData, [ bitmapData ] );
}

// when all the tests are done
function logResults() {
  const total = latencies.reduce( (total, lat) => total + lat );
  const avg = total / latencies.length;
  console.log( "average latency (ms)", avg );
  console.log( "first ten absolute values", latencies.slice( 0, 10 ) );
}

function generateWorkerURL() {
  const content = `onmessage = e => postMessage( e.data, [e.data] );`;
  const blob = new Blob( [ content ], { type: 'text/javacript' } );
  return URL.createObjectURL( blob );
}

Running 1000 tests leads to an average of <1.2ms per tests on my machine (and 0.12ms when not generating a new ImageData every test i.e without GC), while the first run takes about 11ms.
These results imply that the transferring of the data takes virtually no time (it's almost as fast as just waiting for the next event loop).

So your bottleneck is in an other castle and there is nothing to speed up in the messaging part.
Remember that if your main thread is blocked, so will be the handlers that fire from that main thread.

Community
  • 1
  • 1
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • In my tests, before sending the bitmap an init command is sent to the worker so it's not the first call. Also I added a simple "ping" test to see if the response is similar, apparently it is even sometimes getting instantaneous 107 ms, 185 ms. I goes down definitely but it is not consistently low. According to this blog: https://hacks.mozilla.org/2015/07/how-fast-are-web-workers/ Web Workers are fast, about 1 to 2 ms which is definitely way off. – quarks Apr 16 '20 at 03:13
  • I did an isolated tests just, in fact the web worker can get to 1 to 2 ms comm speed but that is if there are no other process running in the script. Apparently the speed of the web worker response is proportional to the number of processes running in the main thread. Or maybe at least the `worker.onmessage` hander is the one getting blocked. – quarks Apr 16 '20 at 04:02
  • Yes of course, the handler runs on the main thread, if your main thread is busy, your handler will get delayed. – Kaiido Apr 16 '20 at 04:04