Benchmarking and code optimization in the browser: Emulators

Update: IE9pp4 gets 9710ms , still slowest

The GameBoy emulator posted over at CodeBase is very impressive. Unfortunately, it’s borderline unusable on anything but Chrome. I know Chrome’s V8 engine is always reported to be the fastest JS Engine, but the difference is ridiculous.

Fast Javascript means pleasing the Tracing algorithm built into browsers. If that algorithm can’t understand what you’re trying to do it will fall back to the interpreter and you don’t want to do that (even with Nitro and J├Ągermonkey). So it was pretty obvious that the problem was just that: Firefox simply didn’t understand the code.

I dove into the JSGB source and found one problem straight away… a problem that is handled remarkably well by Chrome, but even there it makes things about 15% slower. An emulator is basically a big switch statement: If opcode==a then do A, else if opcode==b then do B. In Javascript, you may be tempted to do something like this:

var table={
  a:function(){A;},   
  b:function(){B;},
}
table[opcode]();

That’s exactly what JSGB does. It’s all fine and dandy if you have few calls, but you have to remember that running a function means much more than just running the code: A new context has to be created, the name lookup chain altered, a new analysis profile for tracing created, arguments passed. An emulator does this constantly, so it’s wasting tons of cycles when all you really need is a switch statement:

switch(opcode){
  case a:A;break;
  case b:B;break;
}

The next problem are closures. Going up through the name lookup chain takes its toll, but besides that closures make interpreting Javascript code more complex, making it more likely for the Tracing algorithm to bail out. Apparently on Firefox that’s what’s happening here. An example:

var value=0;
function increment(){
  value++;
}

This may seem simple enough, but already it can cause problems. If you want to make sure, pass everything as an argument. And I mean everything: I ended up even passing the window object as argument. It may seem slower at first, but the Tracer will work and usually take care of it.

var value=0;
function increment(value){
  return ++value;
}

To test this, I wrote my own little emulator (on the page you’ll also find results for Firefox, Chrome and Opera)… it doesn’t emulate a real machine, just some commands that I needed to implement a sample program: 16bit values, 64k memory, 4 registers plus overflow. Enough to bounce a ball around the screen and see how well the browser handles it using either a switch statement or function lookups.

The results are interesting: Firefox is actually fastest when it comes to pure processing (with the switch statement), followed by Chrome and far in the distance Opera. When you’re adding screen updates into the mix (timeouts) the picture changes a bit: Opera now takes first place since it’s able to use the whole timeout period to calculate ahead, but that’s because my sample program is very short. It wouldn’t be enough for a GameBoy emulator.

6 thoughts on “Benchmarking and code optimization in the browser: Emulators

  1. What’s interesting is that this is the area where the short benchmark test cases (for example, those mostly found at jsperf.com) actually play an important role. When pressing the boundaries of the javascript interpreter, using repetitive code, these benchmarks suddenly do matter. Where they in most “apps” don’t really.

    It makes for an interesting optimilization case, that’s for sure ­čÖé

  2. That’s one way to look at it… or you could say that general-purpose processing benchmarks aren’t very useful anymore in the tracing era: A meaningful benchmark would need a sample implemented multiple times with increasing complexity for the tracer and report the untraced result, the traced result and the level of complexity where the tracer starts to fail.

Leave a Reply

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


*