Jump to content
  • Advertisement
  • 12/15/13 06:51 PM
    Sign in to follow this  

    Writing Fast JavaScript For Games & Interactive Applications

    General and Gameplay Programming

    SacredPixel
    Recent versions of JavaScript engines are designed to execute large bodies of code very fast but if you don't know how JavaScript engines work internally you could easily degrade your application's performance. It's specially true for games that need every drop of performance they can get. In this article I will try to explain some common optimization methods for JavaScript code that I picked up during development of my projects.

    Garbage Collection

    One of the biggest problems in having a smooth experience resides in JavaScript garbage collector(GC) pauses. In JavaScript you create objects but you don't release them explicitly. That's job of the garbage collector. The problem arises when GC decides to clean up your objects: execution is paused, GC decides which objects are no longer needed and then releases them.

    memory usage chrome.JPG Zig-zag memory usage pattern while playing a JavaScript game.

    To keep your framerate consistent, you should keep garbage creation as low as possible. Often objects are created with the new keyword e.g. new Image() but there are other constructs that allocate memory implicitly: var foo = {}; //Creates new anonymus object var bar = []; //Creates new array object function(){} //Creates new function You should avoid creating objects in tight loops (e.g. rendering loop). Try to allocate objects once and reuse them later. In languages like C++ sometimes developers use object pools to avoid performance hits associated with memory allocation and fragmentation. The same idea could be used in JavaScript to avoid GC pauses. Here you can find out more about object pools. To better demonstrate implicit garbage creation, consider following function: function foo(){ //Some calculations return {bar: "foo"}; } Each time this function is called it creates a new anonymous object that needs to be cleared at some point. Another performance hit comes from using array shorthand [] to clear your array: var r = new Array("foo", "bar"); //New array filled with some values, same as ["foo", "bar"] r = [];//Clear the array As you can see, the second line creates a new array and marks the previous one as garbage. It's better to set the array length to 0: r.length = 0; Functions can wake up the GC as well. Consider the following: function foo(){ return function(x, y){ return x + y; }; } In above code we return a function refrence from our foo function but we also allocate memory for our anonymous function. The above code could be rewritten to avoid GC: function bar(x, y){ return x + y; } function foo(){ return bar; } An important thing to be aware of is that global variables are not cleaned up by the garbage collector during the life of your page. That means objects like the above functions are only created once so whenever possible use them to your advantage. Also globals are cleaned up when users refresh the page, navigate to another page or close your page. These are straightforward ways for avoiding performance hits that come from GC but you should also be aware of other JavaScript library functions that may create objects. By knowing what values are returned from your library functions you could make better decisions about designing your code. For example, if you know that a library function may allocate memory and you use that function in a performance-critical section of your code you may want to rewrite or use a similiar but more effecient function.

    JavaScript Internals

    JavaScript engines do some preparation on your code (including some optimizations) before execution. Knowing what they do behind the scenes will enable you to write generally better code. Here is an overview of how two popular JavaScript engines (Google's V8 and Mozilla's SpiderMonkey) work under the hood: V8:
    • JavaScript is parsed and native machine code is generated for faster execution. The initial code is not highly optimized.
    • A runtime profiler monitors the code being run and detects "hot" functions (e.g. code that runs for long time).
    • Code that's flagged as "hot" will be recompiled and optimized.
    • V8 can deoptimize previousely optimized code if it discovers that some of the assumptions it made about the optimized code were too optimistic.
    • Objects in V8 are represented with hidden classes to improve property access.
    SpiderMonkey:
    • JavaScript is parsed and bytecode is generated.
    • A runtime profiler monitors the code being run and detects "hot" functions (e.g. code that runs for long time).
    • Code that's flagged as "hot" will be recompiled and optimized by Just-In-Time(JIT) compiler.
    As you can see, both engines and other similiar engines applies common optimizations that we can take advantage of.

    Deleting Object Properties

    Avoid using the delete keyword for removing object properties if you can. Consider the following code: var obj = { foo: 123 }; delete obj.foo; typeof obj.foo == 'undefined' //true It will force V8 to change obj's hidden class and run it on a slower code path. Same is true about other JavaScript engines that optimize for "hot" objects. If possible, it's better to null the properties of object instead of removing them.

    Monomorphic Variables

    Whenever possible try to keep your variables monomorphic. For example don't put different objects with different hidden classes in your arrays. Same applies to properties and function parameters. Functions that are supplied with constant parameter types perform faster than the ones with different parameters. //Fast //JS engine knows you want an array of 3 elements of integer type var arr = [1, 2, 3]; //Slow var arr = [1, "", {}, undefined, true]; The below function can be called with different parameter types(ints, strings, objects, etc) but it will make it slow: function add(a, b){ return a + b; } //Slow add(1, 2); add('a', 'b'); add(undefined, obj);

    Array of Numbers

    Using an array is usually faster than accessing object properties. This is particularly beneficial when the array contains numbers. For example it's better to write vectors using arrays than with objects with x, y, z properties.

    Arrays with Holes

    Avoid "holes" in your arrays. It will make things slower than it should. Holes are created by deleting elements or adding elements out of range of the array. For example: var arr = [1, 2, 3, 4, 5];//Full array delete arr[0];//Creates hole arr[7] = 1; //Creates hole var hArr = [0, 1, 2, 3, /* hole */, 5];//Holey array

    Pre-allocating Large Arrays

    Current implementations of SpiderMonkey and V8 favor growing over pre-allocating large arrays (with more than 64K elements). Keep in mind that this is largely implementation-dependent as some implementations such as Nitro(Safari) or Carakan(Opera) favors pre-allocated arrays.

    Object Declaration

    I can't give you single method for creating objects as it's very engine-dependent but you can see current performance test reults for yourself here and decide what's best for your application.

    Integer Arithmetic

    Use integers where possible. Because most programs use integers, modern JavaScript engines are optimized for integer operations. In JavaScript all numbers are Number type thus you can't directly specify storage type (int, float, double, etc) like other strongly typed languages. If your application is math heavy, one unintended floating point artimetic can degrade your application performance and it can spread through your application. For example: function halfVector(v){ v[0] /= 2; v[1] /= 2; v[2] /= 2; } var v = [3, 5, 9]; halfVector(v); In a strong typed language like C++ where we used int type we would get [1, 2, 4] as result but in our case we implicitly switched to floating point math in our code. JavaScript engines use integer math operations where possible and its because modern processors execute integer operations faster than floating point operations. Unlike other objects and floating point values, common integer values are stored in memory where they don't require allocation. To tell JavaScript engine we want to store integer values in our array in above example we could use bitwise or operator: function halfIntVector(v){ v[0] = (v[0] / 2) | 0; v[1] = (v[1] / 2) | 0; v[2] = (v[2] / 2) | 0; } Result of the bitwise or operator is an integer, this way JavaScript engine knows that it should not allocate memory.

    Floating Point Values

    As stated in previous point, any time a floating point number is assigned to an object property or array element a memory is allocated. If your program does lots of floating point math these allocations can be costly. Although you can't avoid allocation for object properties you can use typed arrays (Float32Array and Float64Array). Typed arrays can only store floating point values and JavaScript runtime can access and store the values without memory allocations.

    Conclusion

    There are many other ways to optimize your code but I think this should be enough to get started. Just keep in mind that you should always profile your code and optimize for the portion that takes the most time to execute.

    Article Update Log

    19 Dec 2013: Updated the article with some tips for integer/float values 12 Dec 2013: Initial release


      Report Article
    Sign in to follow this  


    User Feedback


    This came just in time! Been playing with JS the past few days for the first time in years, so I'm trying to figure out the best way to handle about basic tasks and such.

    Some no-brainers here (if you understand GC), but also some catches. All very helpful in the end. I love how you related it to individual engines and explained what they're doing with your code.

    Share this comment


    Link to comment
    Share on other sites

    Very good and informative article. I am starting to learn JavaScript for a project with some friends and these sound like good things to keep in mind. Especially since performance is our biggest concern and my code will be running, ideally, dozens of times a second.

    Share this comment


    Link to comment
    Share on other sites

    There is some good advice here, but I get the feeling that some things could still be explored in more detail. As it stands, the title is a bit misleading, as I was expecting more content. This could become a good article since you seem to know what you are doing.

    Share this comment


    Link to comment
    Share on other sites
    Could have gone into math libraries a bit further. The things you went into reflect exactly what we had to deal with in a past project. Getting high-performance math code - especially considering that they generally produce or manipulate small arrays - is non-trivial. We had to do things like pre-allocate temporaries in "module" scopes in order to ensure there was no need to ever allocate during gameplay.

    Share this comment


    Link to comment
    Share on other sites

    Maybe addendum to the good info:

    I remember a talk at GDC-EU 2012 where a programmer stated that holding values in int variables wherever possible can save a lot of performance and how hard it was to make sure, as a programmer, that variables stay int and not get converted in memory by your code.

    I follow this advice in general and have an extra look while refactoring but unfortunately I never did any measurements, so this may or may not hold up today.

    Share this comment


    Link to comment
    Share on other sites

    There is some good advice here, but I get the feeling that some things could still be explored in more detail. As it stands, the title is a bit misleading, as I was expecting more content. This could become a good article since you seem to know what you are doing.

    Thanks, Please let me know what areas could be improved.

     

     

    Could have gone into math libraries a bit further. The things you went into reflect exactly what we had to deal with in a past project. Getting high-performance math code - especially considering that they generally produce or manipulate small arrays - is non-trivial. We had to do things like pre-allocate temporaries in "module" scopes in order to ensure there was no need to ever allocate during gameplay.

    Good point, I think this could be an article on its own but I'll try to add more info here

     

     

    Maybe addendum to the good info:

    I remember a talk at GDC-EU 2012 where a programmer stated that holding values in int variables wherever possible can save a lot of performance and how hard it was to make sure, as a programmer, that variables stay int and not get converted in memory by your code.

    I follow this advice in general and have an extra look while refactoring but unfortunately I never did any measurements, so this may or may not hold up today.

    Seems this technique is what most compiled Asm.js code use. By using int for storage enables browsers to convert code directly into assembly and it also gets rid of GC. I don't know if its true outside of Asm.js domain but using int is generally faster than strings or objects.

    Share this comment


    Link to comment
    Share on other sites

    Do you know any resources or examples of profiling/performance testing of JS? What does the procedure look like?

     

    I really liked the graph :)

    Share this comment


    Link to comment
    Share on other sites

     

     

    Seems this technique is what most compiled Asm.js code use. By using int for storage enables browsers to convert code directly into assembly and it also gets rid of GC. I don't know if its true outside of Asm.js domain but using int is generally faster than strings or objects.

    This was before the great asm-craze. What struck me was how strongly this topic was stressed.

    As said, I never ran into a problem with that, maybe because of sufficient overall coding style, but it was suggested that having some vars accidentally converted to float at runtime grinded his animations on screen to a programmers equivalent of a snail ;-)

    Share this comment


    Link to comment
    Share on other sites

    Good, +1.

     

    Perhaps it would also be helpful to have an article which explains (with examples) how we can profile a Javascript game in (e.g.) Firefox or Chrome. As far as I know, both browsers have some kind of profiler built-in, maybe there are alternatives which are more helpful.

    Share this comment


    Link to comment
    Share on other sites

    Very interesting article, but it raises some new questions. If I can sum up the GC part of your post, we can say having long-term life variables is better than short-term (and anonymous functions, immutable etc.). It can be a pain for good code design, so I guess it's recommended to optimize only hotspots when necessary.

     

    By the way, GC engines can evolve, and they will of course. When looking at the java GC, we can see there are efforts to make short-term variables performing as well as long-term, in order to reduce the need of optimizing against code design. We can hope the same will happen for JS engines.

    Share this comment


    Link to comment
    Share on other sites

    Great article.

     

    I did some test on jsperf.com and I found objects are fast than arrays but typedarrays are faster than objects, at least on Chrome 34

     

    http://jsperf.com/raw-array-vs-typedarray

     

    http://jsperf.com/raw-object-vs-raw-array-vector

     

    (this one array will obviously lose as this.array is still a properties access).

    http://jsperf.com/object-vs-array-vector

     

    Anyway, you might want to update to point out that you have to use TypedArrays to get the speed?

    Share this comment


    Link to comment
    Share on other sites


    Create an account or sign in to comment

    You need to be a member in order to leave a comment

    Create an account

    Sign up for a new account in our community. It's easy!

    Register a new account

    Sign in

    Already have an account? Sign in here.

    Sign In Now

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!