Optimizing JavaScript for extreme performance and low memory consumption

April 30, 2009 – 10:10 pm Tags: ,

While making WidgetCity, I had to use various measures to speed up the code. In this post, I’ll show you the tricks I learned – some of which I haven’t seen mentioned anywhere else before.

A lot of performance optimization tips for JavaScript code involve things that you more typically see in web sites, like minimizing the amount of DOM modifications. But this case was different in that it was the script itself that had to run faster – it didn’t work with the DOM or anything, just processed a lot of data.

Finding the source of the problems

Before code optimization can take place, it should be profiled to find out what code is running slowly. The most useful tool for this when working with JavaScript is definitely Firebug’s Profiler feature. Without it, I probably would have much more difficulties in finding out problems and testing how changing things affect the speed.

You can find Firebug’s profiler in the Console tab. Just hit the profiler button once to begin the profiling, and hit the button again to stop. After stopping, you will receive a informative list of the functions which had been called during the profiling and how long each of them took.

Problem 1: Iteration, the amount of data

The first problem in WidgetCity was the sheer amount of data it had to store for the simulation. A full 128×128 tile map would require 16 384 iterations to be completely processed. This may not seem a very large amount, but just iterating the data, without doing anything else, caused a bigger hit than it would in most other languages.

As odd as it might sound, you can use a reversed do-while loop to speed up iteration – instead of using a for loop and counting up, you use a do-while loop and count down.

var i = data.length;
do {
 /* some code */
} while(--i);

Why does this run faster? Apparently the removal of the simple condition used to test when to end the loop makes most of the difference, and the fact that you do –i instead of i– also accords for some.

The problem with this approach is that it isn’t always applicable.

Problem 2: Calling functions

A quite surprising find was that calling functions added very significant overhead. Just having a single function call inside one of those large for-loops could add a lot of processing time.

How to solve this? Inline the function’s code inside the loop.

Instead of calling the function in the loop, you would simply move the code of the function inside the loop itself. This does have a large downside by reducing the readability of the code, and possibly introducing code duplication if the same function call is used in more than one place.

This was one of the biggest performance boosters I found, which in this case was more important than code readability.

Problem 3: Memory limitations

Due to the low amount of memory available on the Nokia N95, the application ran out of memory on a few occasions.

First, the original map size was 300×300, which made the phone run out of memory immediately when creating the map. There was no other fix to this than to reduce the size of the map to the final 128×128. Using a 300×300 map could also have had other performance repercussions, as the amount of data it held would’ve been much larger.

Second, when implementing saving and loading games, the app would again run out of memory. This was probably because the data of the map was being serialized into JSON.

Remco Lanting came up with the idea that solved this: Splitting the data in smaller pieces.

Instead of saving the whole map array of 128×128 in one shot, the code saves it in 8 pieces. This way the size of the serialized JSON string is kept smaller, and the app won’t run out of memory.

Same is done when loading: As the JSON data is saved as 8 separate “blocks”, it is also eval’d back into the array separately.

More optimizations: Dividing and flooring numbers

A part of the game logic also required dividing some numbers and making sure the result was a full number.

Typically this requires you to first divide the number, and then apply Math.floor. As mentioned, calling functions can be expensive.

There is one “neat” trick for this, though. “Neat” because it can be very confusing for people who aren’t familiar with the syntax, and it does make the code somewhat more difficult to read.

The trick involves using a bit-shift:

var foo = 10;
//for most purproses, this is the same as doing Math.floor(foo / 4)
var result = foo >> 2;

Doing >> 2 is, as mentioned in the comment, pretty much the same as first dividing by 4 and then calling Math.floor. But instead of two operations, you only have one, so it can be a bit faster.

If you don’t understand binary math, bitshifts can be difficult to understand. Put simply, if you do >> with 1, it’s the same as division by 2, 2 is division by 4, 3 is division by 8, and 4 is division by 16 and so on.

As usual, there’s a good Wikipedia article on this, which is a good resource to read if you want more information on this.

Even more: Wrap code in anonymous functions

This one was suggested by fearphage – wrap all code in anonymous functions, even if they weren’t actually doing any globals.

For whatever reasons I can’t properly explain, this also affected the execution speed of the scripts.

So whenever you have code in a JS file, remember to put it inside a self-executing anonymous function like this:

(function(window) {
/* all code in your file goes here */
})(window);

You can also make the function take the window object as an argument for a small possible enhancement.

Lastly: Reduce scope traversal

This one adds on the previous one, again suggested by fearphage: Add local variables inside the anonymous function for commonly used functions, like Math.round or Math.random

(function(window) {
var round = Math.round;
var random = Math.random;
/* all code in your file goes here */
})(window);

Summary

So there are various ways of speeding up just JavaScript execution speed, when you don’t have anything like DOM in the way.

In addition to what I showed here, there was one more thing I tried: Seeing if there is any difference between calling an object function vs. an instance function:

var x = new Foo();
x.someFunc();
//or
bar.someFunc();

I was not able to get a difference between this, so if you like using classical OOP style instances instead of “static” functions in objects, go for it.

Just remember, that you should always profile your code before optimization and after optimization, to see where the slow parts are and if your modifications had any effect.

Also consider that many optimizations can make maintaining the code more difficult, so if you don’t absolutely need the speedup, it may better to not do it.

Share this:
  1. 26 Responses to “Optimizing JavaScript for extreme performance and low memory consumption”

  2. Sometimes we want to clear the container and is used for most of the innerHTML. According to my tests came that innerHTML = “” is not always faster, especially in Firefox. It is better to use such a solution:

    function clearElement(obj)
    var e = obj.cloneNode(false);
    obj.parentNode.replaceChild(e, obj);
    return e;
    }

    By Cezary Tomczyk on May 1, 2009

  3. Did you use any of the optimization tips I gave you on http://my.opera.com/community/forums/findpost.pl?id=2969687 ?

    By Mathieu 'p01' Henri on May 5, 2009

  4. I didn’t actually even notice you had replied :/

    I actually did try most of those myself though. Making the arrays into a 1-dimension one didn’t have an effect, and the comparisons and variables didn’t change things either.

    By Jani Hartikainen on May 5, 2009

  5. Similar to your bit shift tip, is another that you can use that I would think (in accordance with your findings) would also be optimized: using & 1 to determine even/odd (say, for zebra striping).

    Instead of:

    if ( x % 2 ) { … }

    This produces the same result:

    if ( x & 1 ) { … }

    It would just make sense that it’d be cheaper to simply compare the 1’s place instead of dividing by 2 and then checking the remainder.

    By ken on May 10, 2009

  6. Another “trick” that you may be interested in, is using array primitives when possible. Specifically, in some instances you can replace a loop with a single method call using apply or call.

    “So, it appears that method is quicker on very large arrays…”

    I used Array.prototype.join.apply, and if you’re indeed doing lots of heavy-lifting with arrays, you should definitely give this a read:

    http://www.learningjquery.com/2009/03/43439-reasons-to-use-append-correctly/comment-page-1#comment-71544

    By ken on May 10, 2009

  7. I can think of other things you didn’t suggest.

    You can floor a positive number without using Math.floor, simply by bitwise-or-ing it with 0, like: (0 | value). This actually has the effect of type-casting an floating-point value internally (in the JavaScript engine) to an integer, if it’s not already one. However, as you said, for division of positive numbers by powers of two, bit-shifting makes more sense. In fact, all bitwise operations cast to integer in the same way.

    By Dermot on May 23, 2009

  8. Hi,
    wonderfull post!
    I read your post and I wanted to ask if you agree, can I translate it in my language (Italian) on my blog?

    Thank you

    By Aleprex on Jun 24, 2009

  9. Aleprex, feel free to translate it on your blog as long as you point out the original was written by me and link back here.

    By Jani Hartikainen on Jun 25, 2009

  10. Is there an order that conditions are evaluated when there is more that 1 condition;

    eg:

    if (Test1==true||Test2==true||FuncTest()==true) {
    // do something
    }

    Do the “Test” values get evaluated in left to right order? Does it stop evaluating the conditions as soon as it finds a positive or do all conditions get tested first?
    I want to avoid running the “FuncTest()” function if Test1 or Test2 are true.

    I need to do many levels of nested checks like this within a loop so nesting the FuncTest() check will result in many levels of if {…} else {…} splits which will result in either lots of redundant code or many nested function calls.

    if (TA1==true||TA2==true)
    TA()==true) {

    By David on Oct 7, 2009

  11. JavaScript uses short-circuiting. The conditions are evaluated from the to right, and as soon as the condition can pass/fail, nothing else is evaluated.

    //if foo returns true, bar is never called
    if(foo() || bar()) 
     
    //if foo returns false, bar is never called
    if(foo() && bar())
     
    //and so on.

    By Jani Hartikainen on Oct 7, 2009

  12. I want to avoid running the TA(), TB(), TC() … functions if I can without having to do this:

    if (TA1==true||TA2==true){
    if (TB1==true||TB2==true) {
    if (TC1==true||TC2==true) {

    } else if (TC()==true) {

    }
    } else if (TB()==true) {
    if (TC1==true||TC2==true) {

    } else if (TC()==true) {

    }
    }
    } else if (TA()==true) {
    if (TB1==true||TB2==true) {
    if (TC1==true||TC2==true) {

    } else if (TC()==true) {

    }
    } else if (TB()==true) {
    if (TC1==true||TC2==true) {

    } else if (TC()==true) {

    }
    }
    }

    By David on Oct 7, 2009

  13. WOW, Thanks Jani for your fast response!

    By David on Oct 7, 2009

  14. Absolutely GREAT! I love this! I’m always keen to optimize my scripts, this has been very helpful and interesting

    By GREAT! on Apr 1, 2010

  15. On looping, it’s usually faster to decrement.

    Modern compilers and interpreters ‘hide’ that from you by optimizing the actual assembly instructions that get kicked out, but if you’ve never written assembly, it’s not something you’d immediately think about.

    Same with inline functions, your average C compiler optimizes it for you.

    No matter what language, you can’t dodge the fundamentals. You may have layers of abstraction that hide things like this (really good preprocessors and compilers), but it’ll always be there.

    By Omachonu Ogali on Jun 20, 2010

  16. Great article, however, one thing to mention is that if you are iterating over a multi-dimensional array with many elements (600 rows and 20 columns for example), and using a string concatenation inside the loop, be careful. Something like this

    var tableStr = “”;
    for(var i=0; i < 600; i++) {
    for(var j=0; j<20; j++) {
    tableStr += elts[i][j];
    }
    }

    where elts is an array of elements

    IS BAD, the performance is bad in IE7.

    If you change this to

    var tableStr = "";
    for(var i=0; i < 600; i++) {
    var row="";
    for(var j=0; j<20; j++) {
    row += elts[i][j];
    }
    tableStr += row;
    }

    In my case, the performance in case 1 was 20+ seconds in IE7, and under 2 seconds in case 2 in iE7. FF's superior engine didn't experience much difference.

    Hope this helps someone.

    By sarsnake on Jan 13, 2011

  17. original example:
    var i = data.length;
    do {
    /* some code */
    } while(–i);

    should be change to:
    var i = data.length – 1;
    do {
    /* some code */
    } while(i–);

    By Ricardo Tapia on Jan 9, 2012

  18. Not sure about bit-shifting being faster in javascript since it has to convert 64 bit float to integer execute the shift operation then convert back to 64 bit float.

    By Steve Terpe on Jul 27, 2012

  19. @Steve it was definitely faster when I benchmarked it. Of course the current engines have advanced with things again, so the difference might not be so obvious these days :)

    As with any optimizations, remember to always measure before and after optimizing.

    By Jani Hartikainen on Jul 27, 2012

  20. I can’t believe that this post is still available — most blog posts seem to fall off the face of the internet after a couple of years.

    Perhaps it’d be good for an update? Now that we have sites like jsperf.com, it’d be easier to quantify the results :)

    By ken on Aug 8, 2012

  1. 7 Trackback(s)

  2. May 4, 2009: I wrote a Sim City clone in JavaScript | CodeUtopia
  3. May 11, 2009: links for 2009-05-11 « pabloidz
  4. Jun 10, 2010: Optimizando JavaScript para un rendimiento extremo y bajo consumo de memoria » elBl√ęg – Interactividad, usabilidad y web
  5. Oct 28, 2011: JavaScript optimieren | WebCode Blog
  6. Mar 15, 2012: Javascript Optimizations | Mike Newell
  7. Apr 9, 2013: Object Oriented Javascript best practices? [closed] | Everyday I'm coding
  8. Dec 1, 2014: How to: Object Oriented Javascript best practices? | SevenNet

Post a Comment

You can use some HTML (a, em, strong, etc.). If you want to post code, use <pre lang="PHP">code here</pre> (you can replace PHP with the language you are posting)