Web Worker Example: The Rationals

The Web Worker library I’m writing for GWT is getting to the point that it needs some examples. At least one of them needs to be simple enough that what is being done doesn’t distract from how it’s being done. You can see various examples like this in the official (draft) specification and over at the MDC. The problem with these examples, I think, is that they don’t really motivate the use of Workers; the task is usually completely unrealistic, intentionally implemented naively, and the gain so small that maintaining two separate versions of a codebase—one for browsers with Worker support and one for those without—ends up on the wrong end of the cost/benefit analysis.

But it might work for us. So, here’s a ridiculously unrealistic task, coded in the most straightforward way possible, and that runs perfectly well in Internet Explorer (8) without any Worker support. The reality is that instructive examples are of course necessary, so I’m going to trust to other demos to inspire excitement. In the meantime, I get another chance to play around with hyperbinary numbers. I’ve mentioned this sequence and its connection to the rational numbers before, but to recap: I first came across it on Division by Zero (still waiting for the rest of that series) and the original paper by Calkin and Wilf is here.

The example application calculates and displays a succession of rational numbers (fractions), as ordered by the Calkin-Wilf enumeration of the rationals. That sequence has several very interesting properties, but notably it contains every single (positive) rational number, in reduced form, exactly once. The example page doesn’t quite live up to the “every” part, but it does go on for a while. You can see it here: An Enumeration of the Rational Numbers.

By default, it actually starts at the 10-millionth rational number to make the demonstration a little more computationally taxing, but that can be changed if you want to see how it all begins (for the less motivated: “1″).

The GWT-y details

Right… Anyway, the “Rationals” GWT project is divided into two modules. The Worker module (RationalsWorker) extends a special Worker EntryPoint. It repeatedly calculates a new hyperbinary number, combines it with the last, and returns the result to the parent script:

public class RationalsWorker extends IterativeWorkerEntryPoint {
  @Override
  public void onWorkerLoad() {
    // nothing to initialize
  }
 
  @Override
  public int execute() {
    // calculate numerator and denominator...
    postRational(numerator, denominator);
    return DELAY_MS;
  }
 
  /**
   * Creates a message containing calculated numerator and denominator and
   * send to parent context.
  */
  public void postRational(int numerator, int denominator) {
    String data = Json.strigify(RationalNumber.create(numerator, denominator));
    postMessage(data);
  }
}

The main module (Rationals) creates a Worker object out of the RationalsWorker module and then just waits to receive new numbers, inserting them into the page as they arrive:

public class Rationals implements EntryPoint, MessageHandler {
  Element numerator;
  Element denominator;
 
  // Worker module definition
  @WorkerModuleDef("gwt.ns.sample.rationals.RationalsWorker")
  interface RationalWorkerFactory extends WorkerFactory { }
 
  @Override
  public void onModuleLoad() {
    numerator = RootPanel.get("numerator").getElement();
    denominator = RootPanel.get("denominator").getElement();
 
    // Worker creation
    RationalWorkerFactory factory = GWT.create(RationalWorkerFactory.class);
    Worker worker = factory.createAndStart();
 
    worker.setMessageHandler(this);
  }
 
  @Override
  public void onMessage(MessageEvent event) {
    RationalNumber newNum = Json.parse(event.getData());
    numerator.setInnerHTML(newNum.getNumerator() + "");
    denominator.setInnerHTML(newNum.getDenominator() + "");
  }
}

The compilation process is better described in the design document (which I’ll be finishing real soon now), but I’ll describe it here in brief. When the Rationals module is compiled, the Web Worker library handles the loading of the Worker through the magic of deferred binding. In code destined for browsers with native Worker support, the Worker module is compiled using a special linker, and a URL for the resulting script is inserted into the parent module so the browser can load it at runtime. For browsers without Web Worker support, the Worker module’s entry point is actually inserted directly into the main module, along with some machinery to simulate the behavior of a Worker. In both cases, a “Worker” object is returned and the parent module can proceed in full ignorance of the implementation.

The Rationals example source code is available in the project repository. The host page is down in the war directory.

Benefits and Runtime Behavior

As I noted above, this example runs well in Internet Explorer 8. This comes down to how it was written. Even though code executed continuously inside a Worker would not block the main event loop, the same code run in a faux-Worker would. Authors are perfectly free to further differentiate by loading different code based on browser type. However, the first goal of this library was to provide a “thread-like” context for executing the same code regardless of browser—using a real Worker if available—in a way that requires only a minimum of boilerplate and provides full support for development-mode debugging.

So, if the same code is going to be run everywhere, it can’t be written in a way that disrupts responsiveness in older browsers. For this example, the calculation of rational numbers is divided by the usual setTimeout/callback scheme to keep the UI (fairly) responsive in IE, even though it is being done in the main event loop and nearly pegging one core on my old machine:

IE CPU use at 46%

Chrome is a different story. The same code uses about the same execution resources (generating rationals at a greater rate) but this time it is running in a Web Worker:

Chrome CPU use in Worker

As can be seen, this leaves the actual page (“Tab: …”) doing almost nothing. If we imagine that this is a script that must be run, regardless of platform, Chrome leaves the page free to do something else, at the author’s discretion, or to simply demonstrate the silky smoothness of the future while IE updates at 15 frames per second.

Stray Observations

In Firefox versions prior to 3.6, TraceMonkey seems to struggle with some scripts in a Worker context while having no problem when they are run normally. This really calls for a test page, with a reliable Worker on/off switch, to determine exactly which browsers (× which kinds of problems) should receive native or emulated implementations.

With that said, Firefox (and especially Minefield) eats this demo for breakfast. The inner loop is mostly simple arithmetic and array accesses, so I wonder if something else is holding v8 back. If there’s a way to profile a Web Worker using Firebug/Development Tools, I haven’t found it yet.

Demo: An Enumeration of the Rational Numbers

This entry was posted in Uncategorized and tagged , , , , , , , , , , , , , . Bookmark the permalink.

2 Responses to Web Worker Example: The Rationals

  1. Pingback: Re: [gwt-contrib] Re: RFC: Web worker proposal and proof of concept

  2. Pingback: Transform Module Rewrite and Example « Extremely Satisfactory Totalitarianism

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">