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.
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.
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:
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:
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:
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:
Functions can wake up the GC as well. Consider the following:
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:
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 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:
Avoid using the delete keyword for removing object properties if you can. Consider the following code:
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.
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.
The below function can be called with different parameter types(ints, strings, objects, etc) but it will make it slow:
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.
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:
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.
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.
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:
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:
Result of the bitwise or operator is an integer, this way JavaScript engine knows that it should not allocate memory.
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.
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.
19 Dec 2013: Updated the article with some tips for integer/float values
12 Dec 2013: Initial release
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.
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.
- 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.
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