Follow me on Twitter Follow me on GitHub
Chandler Prall Thoughts & Experiments for the Web

Beating 60fps in Javascript

UPDATE: It’s been pointed out in the comments that because most LCD displays have a refresh rate of 60hz, attempting to get anything higher than 60fps is mostly pointless. However, the method I discuss isn’t confined to just FPS but is rather about removing the inherent delay when using timers while still allowing the browser to update UI and allow user interaction. Also, if you have code running at 20fps this method may help you glean a few more frames each second.

UPDATE 2: Apparently Internet Explorer 10 will be implementing the setImmediate() function which would be the best method to use for quick loops. I can’t find any info on whether Firefox or Chrome is planning on adding this method anytime soon, but I would suspect it will be included in their builds soon enough. Here’s the W3C spec.

I’ve been working on a WebGL game which I plan to release Friday evening; during the development I have been investigating ways of decreasing time between render() calls. Less time in between means a higher FPS, which gives a smoother user experience. There has been a lot of discussion lately over the best method to run a main() loop. The common answer seems to be requestAnimationFrame, as Paul Irish discusses here. The requestAnimationFrame method aims to provide a callback every ~16ms, or 60fps. Before requestAnimationFrame was available, Javascript developers had to use setTimer to schedule the run through main().

In testing the different functions I found that setTimer was able to provide a finer fidelity, yielding between 133% and 330% increase of speed depending on the browser used (Chrome’s V8 certainly living up to its reputation in my tests). But I wondered if there was an even more powerful method, one which could get around the milliseconds wasted by calls to setTimeout(). In John Resig’s new book, Secrets of the JavaScript Ninja, in the section titled Minimum Timer Delay and Reliability he shows that across most browsers “it’s safe to say that the minimum delay interval is around 10-15ms.” The tests I ran on my home system (results listed below) show setTimeout returning on average 94fps (just over 10ms) in Firefox and 206fps (4.8ms) in Chrome. Let’s say the delay between setInterval() calls is always 5ms and render time is a very unrealistic but incredibly optimal 0ms. That means your scene will be rendered at a blazing 200fps, but it also means you’re wasting 5ms after each render.

I started wondering how this delay could be lessened or if it could somehow be removed altogether. Programs which execute out of the browser (C++, .NET, Java, etc) all have main program loops which execute one right after each other. You can do the same thing in Javascript by using while(true) { main(); } but doing so will lock up the browser in an infinite loop as it will be stuck in your main() loop and never repaint the page or let the user interact. My first thought was to mix the infinite-loop and timers. For my test, I executed the main() function 15 times before calling setTimeout(). This method doubled my fps in Firefox but had no effect whatsoever in Chrome. Surprisingly (to me at least), this had no effect on the browser interface’s feedback – I had expected it to lock up a bit. However, I doubt this method would allow the browser to paint all 15 individual WebGL scenes before getting to the setInterval(). I suspect all 15 would be rendered, the last one displayed, and then the timeout occurring.

Next idea on minifying the delay does remove timers altogether while still providing a way for the browser to continue updating its interface and executing other code on the page. This method relies on using a call to window.postMessage This is a wonderful new method defined in HTML5 which allows one window to communicate and pass messages to another window. At the end of my main() loop I can call window.postMessage(”, ‘*’); – this simply passes an empty message to the current window. If this was everything, my code would stop executing at the end of the main() loop. However, by adding an event listener (window.addEventListener(‘message’, main, false);) I also catch any messages posted to my window by immediately calling my main() loop. The side affect of this is that in between making the postMessage() call and having my main() loop executed again, the browser can update its UI and execute any other scheduled code, thereby providing no interruption to the user while still having my main() loop executed immediately. In my test script, this method executes my main() loop at 880fps in firefox and an astounding just-under-1000fps in Chrome.

That’s all well and good but it’s just a test script, right? These sort of numbers can’t in anyway carry over to an actual game execution loop can they? My game runs at ~63fps when using setTimeout(main, 1). When I switch that out for the postMessage() method the fps jumps up to ~330fps. However (and unfortunately), this speed is actually more taxing for my processor which makes the browser somewhat less responsive to user input: from the user’s point of view, the experience is lessened.

Takeaway: While not a perfect (or great) fit for a main() loop, using the postMessage function can allow you to execute tight and/or infinite loops without degrading user experience. These could be used in addition to a main() loop, perhaps increasing the quality of collision detection, or by performing some repeating non-scheduled task such as pathfinding.

The code I ran for the tests is up on jsFiddle.

Here are the test results for the four separate methods used. System 1 has an Intel Core2 Duo E8400 @ 3.00GHz with 2GB of RAM running XP service pack 3. System 2 has an Intel Core i5 @3.2GHz with 4GB of RAM running on Win7 32bit.

Pure Timers requestAnimationFrame Interupted Loop postMessage
System 1 – Firefox 4 95fps 58fps 206fps 880fps
System 1 – Chrome 11 206fps 60fps 206fps 999fps
System 1 – IE 8 64fps 64fps (using polyfill) 64fps - *
System 2 – Firefox 3.6 91fps 91fps (using polyfill) 856fps 995fps
System 2 – Chrome 12 250fps 63fps 250fps 999fps
System 2 – Safari 5 95fps 95fps (using polyfill) 141fps 999fps
System 2 – IE 9 401fps 402fps (using polyfill) 452fps 973fps

* When testing the postMessage() method, IE8 decided to take its ball and go home. The onmessage event was firing but I couldn’t get the Javascript to update the browser’s interface. Buttons still worked fine, just no visual result of the test.

12 Responses to Beating 60fps in Javascript

  1. FremyCompany says:

    As your screen can’t update his visual more than 60 times per second, you’re wasting many CPU for nothing by trying to achieve higher rates. I don’t understand what’s the point here…

  2. Boris says:

    Modern LCD screens refresh at 60Hz. So any painting you do at higher than 60fps is completely wasted: the user will only see 60 updates a second.

    • Johan says:

      Well that’s not totally true, it’s true you see 60 fps, but there is any noticeable difference between the 60 fps and the basic 24 fps for the brain (that’s why cinema movies are at 23.97 fps). So playing your animation at 60fps is still a waste of cpu.

      • chandler says:

        Except movies, both digital and film, record motion blur. Unless you’re rendering motion blur in your code, you need a faster frame rate and smoother tweening for the user to see that same life-like blur.

  3. chandler says:

    Ah, you both make an excellent point; I completely overlooked the screen refresh rate. Which turns this into just an intellectual quest, unless there’s some algorithm which would usually trigger a Too Much Recursion error which could be chained in this manner.

  4. SarahC says:

    My old monitor is refreshing at 120Hz….. thanks for this!

    Also – as anyone who plays FPS’s will tell you – faster game loops results in better (more accurate) physics, regardless of how fast the visual frame rate is. =)

    Having a main game loop handling AI and physics separate from the graphics loop’s kinda standard.

    • chandler says:

      My next (kinda current?) project is going to require pathfinding over a large & complex area. Thankfully the pathfinding won’t have to be done for each frame, but at least once every few seconds. Webworkers are perfect for these not-quite realtime tasks, but are somewhat difficult to incorporate well even as far as threading concepts go.

  5. @top two comments, Ever consider that not everyone uses a 60Hz LCD? Some of us have higher frequency LCDs (120Hz is the most obvious), or use CRTs which can run at far more obscure rates (I run my CRT at 70Hz, for example).

    Great post chandler, I can always appreciate someone striving for optimization.

  6. Alex Morse says:

    This is a pretty clever method. Obviously by your article it’s non blocking to the UI, which is awesome. Have you done any benchmarking to look at what sort of cpu ramp up this results in? My main concern is battery life (one of the things requestAnimationFrame is supposed to address,) but if you’re going for raw performance this seems like pure win.

    • chandler says:

      It ate up a core of my CPU. One method I want to try to mitigate this is to intentionally inject a few milliseconds pause, for instance:

      main() {
      // Force a 2ms pause
      if (new Date().getTime() – last_render < 2) {
      window.postMessage(”, ‘*’);
      return false;
      }

      [ update / render ]

      window.postMessage(”, ‘*’); // call Main again
      }

      Haven’t tried it, but I’m curious to see if it has any impact on CPU usage. It’s still a tight, infinite loop, however.

  7. Rene Kriest says:

    Pretty cool and funny stuff. Reminds me of the good old times on C64/Amiga when we were hunting the timer cycles to sqeeze out the VIC/M68000. ;)

  8. Pingback: JavaScript Magazine Blog for JSMag » Blog Archive » News roundup: Paper.js, Fathom.js, test262

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>