Web Worker Performance Tips 101

There are many potential benefits to using web workers. They can provide a significant web application performance boost by moving heavy-duty work off the main browser thread. It’s also true that in certain instances you may be slowing down your application in ways you didn’t expect.

Tip #1 – The cost of using a web worker is not free. JavaScript must serialize your data to pass it to a background thread and it must also serialize data when sending it from a background thread back to the main thread. That’s two serialization processes for each round trip and each process takes CPU cycles and time. Depending on what type of data and how large it is you may be surprised how long it can take.

Tip #2 – Not all browsers treat web workers equally. Web worker performance gains in one browser may not represent a similar gain in a different browser. If you are building a cross-browser application, make sure you specifically test and measure each of your web workers in the various browsers that you will be supporting. This often takes developers by surprise. The issue is mainly due to differences in how each browser vendor implements their data serialization algorithms. If you want more information on this, its officially referred to as structural cloning algorithms.

Tip #3 – Measure the total time it takes to use a web worker. Set console.time() before the initializing the worker and set console.timeEnd() where you get a message back. You’ll want to compare these results against running the same code directly on the main thread.

Example:

    console.time("parseTestTimer"); // Start the timer
    
    // Initialize the worker
    var worker = new Worker("ParserWorker.js");

    // Send the data to the worker
    worker.postMessage([first.value,second.value]);

    // Get the data back from the worker
    worker.onmessage = function(result){
        console.timeEnd("parseTestTimer"); // End the timer
        // Do something
    }

Tip #4 – Even using binary transferable objects can have a cost. The Transferable pattern for web workers are designed for high performance, however depending on what you are transferring, the browser, the browser version and the device type (mobile vs desktop) your mileage may vary. In more technical terms this pattern, at least in theory, uses a zero-copy, pass-by-reference approach which is intended to have very low overhead. You should definitely consider testing the transferable objects pattern and compare timing benchmarks against the standard web worker postMessage() pattern. You might as well be thorough, especially since there’s no guarantees of how each browser vendor implemented this functionality under the hood.

Example:

    // Transferrable object pattern using binary data
    worker.postMessage(uInt8Array.buffer, [uInt8Array.buffer]);

Additional References:

Advanced Web Worker Performance – this post provides several important details for determine if your web worker is provide a positive or negative performance gain.

Samples apps demoing no workers, one worker and two workers

MDN – Structured Clone Algorithms

HTML Living Standard – Transferable Objects

Advanced Web Worker Performance

This post offers some advanced considerations on the mechanics of squeezing the best performance possible out of workers. I only mention a few (very) rough benchmarks since the goal is to focus on guidelines and food-for-thought, rather than specifics. It’s also fair to say that not all tasks benefit from using web workers. The best way to know is to test performance with and without the workers.

Processing costs. There are a variety of costs associated with manipulating data. Manipulating data means any task you run against it such as looping, converting, parsing and analyzing. Each one of these tasks should be evaluated for the cost in terms of CPU usage and time spent. By looking at these aspects, you can determine the benefits of having a web worker versus not having a web worker.

Pre-processing – manipulating data before sending it to the web worker.
Peri-processing – manipulating data while doing work on the background thread.
Post-processing – manipulating data after it’s been received back from the web worker.
Total processing – this benchmarks the processing from the very beginning until the very end.

Time stamp before and after each process that handles data. Tools such as console.time are invaluable in identifying actual and potential bottlenecks. Also make liberal use of the developer tools such as Chrome’s CPU profiler that now includes screen captures.

Total CPU and memory usage. There’s often a misperception that just because CPU usage has been outsourced to the background thread that it won’t negatively impact an application or contribute to jank. This is not true, especially on mobile devices that are already CPU and memory constrained. CPU headroom, or the amount available for application processing, is finite and typically doesn’t care whether CPU is used on the main thread or a background thread.

Web apps are wholly dependent on how each browser vendor uses CPUs and manages threads behind the scenes for every browser version and operating system. Using JavaScript you cannot guarantee how the browser will spread CPU load across cores.

In fact, I’ve seen web worker performance improvements made in Chrome that create almost an exact opposite performance decrease in Safari! Talk about a WTF moment.

Cloning costs. Careful consideration should be placed on the costs associated with the act of sending and receiving messages. Sending and receiving isn’t “free”, in fact the amount of CPU consumed can directly cause jank.

Web workers use a cloning algorithm for serializing objects that go in and back out, and cloning uses CPU cycles. Yes, even if you are using transferable objects there may still be performance implications especially when dealing with large amounts of data (>100MBs) as well as on mobile devices. You simply will never know unless you test, test, test.

In some cases, the cloning costs related to both sending and receiving messages may outweigh the performance benefit of using a web worker. If the entire round-trip time and CPU utilization is greater than simply keeping the processing on the main thread – then you probably don’t need a web worker.

Pooled workers. Should you use one worker or several? The only reliable way to know is to experiment. One approach to eke out greater performance is to create a pool of workers. The concept is a task that can be broken up and passed on into smaller concurrent worker tasks and then reassembled after all tasks have completed.

Here’s an example application that retrieves a GeoJSON file, parses it, then displays the data on a map. Open up the developer console and run each application multiple times. You can also download and experiment when this application yourself with increasing and decreasing the size of the thread pool:

Example 1: Parsing using no web workers
Example 2: Parsing using one web workers
Example 3: Parsing using two web workers

With respect to binary versus non-binary data, I’ve seen diminishing returns when using larger number of workers with JSON files. On the other hand, there are a number of posts on the web that show great performance processing image data with increasing numbers of workers.

This also may be dependent on the browsers-threading model and how it uses the number of cores on your device or laptop. How do you know? Test and benchmark.

Re-use workers, or re-create for each loop? There are costs associated with initializing workers. Depending on the type of application, it may benefit you to re-use workers rather than re-create them for each loop. This depends on how complex your web workers are, how many you need to spin up, the type of device they are running on and how often you are using them.

Some applications may run on a timer once every 5 minutes. If that’s the case does it make sense to keep workers sitting unused in memory for that long? Maybe or maybe not. Other applications may only need the workers at startup and there’s definitely no need to keep them around after that. The list of use cases are endless.

Here’s some very rough test results on the time to simply initialize web workers. This example used identical workers that contained 67 lines of code:

Macbook Pro: 2 workers, 0.4 milliseconds on average
Macbook Pro: 4 workers, 0.6 milliseconds on average
Nexus 5: 2 workers, 6 milliseconds on average
Nexus 5: 4 workers, 15 milliseconds on average (border-line UI jank)

As far as computational horsepower, the Macbook used in the tests had 2.6 GHz i7 with 16GB of 1600 MHz DDR3. The Nexus 5 had a Snapdragon 2.26 GHz with 2GB of 800 MHz RAM. Clearly the Macbook outclasses the Nexus. It’s important to note how much longer it took to spin up a pool of workers on a mobile device. That’s an example of an app that would work seamlessly on a powerful laptop, but could produce jank on a mobile device.

Closing Notes. Web workers are fairly straight forward to bolt into an application, but tuning them up to gain the true performance benefits can be a bit more tricky. With a little exploration and experimentation, workers can potentially provide huge benefits for your application’s performance.

Sample Application

[Fixed broken links: July 7, 2016]

Improving Browser performance and stability – will web workers help?

The single-threaded nature of JavaScript is an old tradition that needs to go away. It was great in the wild-west, internet days of the 20th century. But, today we have more complex needs that are being driven by the advancements that are happening around good old JavaScript as we know it, such as…on-going advancements in HTML 5.  

The reason I bring this up is because I’ve been watching the discussion on Web Workers as it has evolved.  It’s a brave attempt to bring a standard for implementing some sanity on this ancient notion of single threading. Now, I do want to say that this post isn’t about debating the merits of web workers, per se. It’s about giving developers better tools on which to build web applications for end users. I’ll be the first to agree that many developers (but not all!), for a variety of reasons, build apps like factories, but without many quality checks.

One argument the pro-single threaded parties claim is that doing away with single-threading will make things even more complicated for the companies that develop browsers and the developers that build apps on them. And, in effect, you’d be giving them (web app developers) free license to create even more terribly built web pages that crash browsers.  For brevity sake, I’m only picking this one out of many possible arguments, as the one that comes up most often in discussions.

I also don’t ever recall seeing a browser vendor themselves saying something like this publicly, but it’s possible.  This is a very weak argument that won’t stand the test of time. Sure, as we build more complex apps then there will be more of both good and bad apps. That’s just the way things work. There’s no way we would ever have a single authority that reviews all web apps before they are published. Perhaps, similar to what Apple does with iPhone apps. Not only would it be impractical, but it certainly seems like it goes against the spirit of the internet and WWW.

I fall into the camp of evolving the tools to better to fit the ever-changing and growing needs of the end users. End users don’t understand the limitations of the browser technology.  They don’t need to and shouldn’t be expected to. All they know is that they want to see ever more visually stunning applications that run well and don’t crash all the time.

Developer tools and technology are much, much more advanced now than when the venerable Mosaic Web Browser hit the scene back in 1993. As an example, all eyes are on HTML 5 (more on that at a later date), and certainly we have the well-known browser plug-ins: Flash and Silverlight, and each has their own development kits. These technologies enable the building of some of the most eye-catching websites, and they really opened people’s eyes on what the web experience should be more like.

Now, I am eyes-wide-open about this. There are some well-documented, but not well understood existing limitations related to the web surfing/development experience as I blogged about here. But, merely saying things should not change because it will become too complicated isn’t a good enough reason to, well…not change.  There are lots of smart people out there that love solving these types of problems.

So, I have a few suggestions of my own for the browser vendors and others to debate and work on. I think web workers are huge step in the right direction. But I also think there’s some other more strategic things that browser vendors could be doing that I think would also help. To me these are just as important as evolving the web standards, perhaps even more so. This is about browser vendors officially providing guidelines for us on how to do our job better:

  • Best Practices Document. All the major vendors should publish web development best practices for HTML and JavaScript development. And, I’m not talking about the W3C standard. That is what’s expect, but not actually what’s implemented. For example, I did a quick search of “web development best practices” using Google and Bing and the very first result I found was a short, not-really-so-helpful article on the Apple web site that was written in 2008!
  • Online HTML/JavaScript Validation engine(s). Each browser vendor should publish their own online HTML/JavaScript validation engine. Or better yet would be if someone builds one site that checks all major browsers in one shot and provides actionable feedback. I’m aware of other types of validators such as this one by W3C for HTML and the like. But, in general right now it’s just a hodgepodge of 3-rd party tools and guesswork as to whether a web app is working right. And, if you are like me and running the web debugger all the time, you’d know how many broken web pages there really are.

References: