Writing Fast JavaScript For Games & Interactive Applications

Published December 15, 2013 by Mohsen Heydari, posted by SacredPixel
Do you see issues with this article? Let us know.
Advertisement
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
Cancel Save
0 Likes 12 Comments

Comments

Ganoosh

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.

December 14, 2013 06:16 PM
ImpulseChimp

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.

December 14, 2013 07:51 PM
Madhed

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.

December 14, 2013 09:32 PM
Tasaq

I would like to see similar article, but more in depth and for C# :)

December 15, 2013 09:42 PM
SeanMiddleditch
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.
December 16, 2013 07:37 AM
Carsten Germer

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.

December 16, 2013 01:37 PM
SacredPixel

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.

December 16, 2013 02:55 PM
mipmap

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

I really liked the graph :)

December 17, 2013 09:06 AM
Carsten Germer

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 ;-)

December 17, 2013 11:40 AM
markr

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.

December 26, 2013 08:22 PM
kremvax

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.

April 02, 2014 08:01 PM
greggman

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?

April 11, 2014 05:50 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!

Some tips for writing fast JavaScript code.

Advertisement

Other Tutorials by SacredPixel

SacredPixel has not posted any other tutorials. Encourage them to write more!
Advertisement