Make your JavaScript apps smoother
A growing amount of real-time content is being written in JavaScript. Ashley Gullen shows how to avoid pauses caused by garbage collection for a smoother experience
This article first appeared in issue 231 of .net magazine – the world's best-selling magazine for web designers and developers.
Modern web applications often involve real-time animation and interactivity – anything from simple roll-over effects, Flash-like banners and animations implemented in HTML5 to complete games. JavaScript is faster than ever thanks to modern JIT compilers in browsers, but there’s still one problem: garbage collection (GC). This can cause pauses, stuttering and poor performance, ruining the smooth real-time experience.
But what is garbage collection? JavaScript enables you to easily create objects. However, there’s no way to delete a given object (the delete operator doesn’t count: it’s for removing properties on objects). Instead, objects are deleted automatically when no longer used.
If you create 1,000 objects but only keep references to 500 of them, at some point the browser will decide to clean up. It will see 500 are not referenced anywhere and are therefore no longer used, so it cleans them up. Some browsers implement clever techniques to make collection as quick as possible, such as generational collectors. But if you create a lot of objects in JavaScript, sooner or later the browser will have to clean up: execution is paused, it spends a while figuring out which objects are still used, and then deletes the rest.
This could take hundreds of milliseconds, or in some cases a full second or more. A smooth animation has to run at 60 frames per second, with just 16ms per frame, so this can create a noticeable pause. If lots of objects are created the browser may have to collect several times per second, making animation choppy. If you’re playing an action game in HTML5, few things are more annoying than a boss appearing, you charge up energy, time the perfect move … then the browser seizes up for a second, you miss your moment, and lose the game!
The perfect solution is to make sure nothing is allocated in the code run for each frame. But this is surprisingly difficult since many innocent looking statements create garbage. Generally the aim is to keep the rate of garbage creation low so that collections are infrequent and don’t have much to clean up.
The main technique to avoid garbage creation is to recycle objects. That means instead of creating lots of objects and throwing them away, create a few objects on startup and re-use them as long as possible. That also means keeping permanent references to objects you would have otherwise thrown away. Caches are important too, since multiple objects can be collected for recycling.
Minimising numbers of objects created also helps improve your JavaScript's performance. Allocations and de-allocations take time; eliminating these by re-using objects can reduce the overall amount of work the browser needs to do, speeding up execution. To avoid garbage creation we need to understand which statements allocate objects – it’s obvious that the new operator creates a new object. However, there are some common shortcuts that also create new objects you should beware of:
{} // same as: new Object()[] // same as: new Array()// also creates a new function!function () { ... }
Don’t forget new allocations are made even when specifying properties or elements, which is common in JavaScript:
// Creates new object with a property{ "foo": "bar" }// Creates new array with 3 elements[1, 2, 3]// Creates 4 new arrays![[1, 2], [3, 4], [5, 6]]
Avoid all these in any code that is called frequently, especially every frame. Instead try to create the object once and permanently reference it. A simple change to make is clearing arrays, where setting the length to 0 is better than assigning a new array:
// BAD: garbage old array, create// new one, and reference thatmy_array = [];// BETTER: re-use same array objectmy_array.length = 0;
Functions can also catch you out. You may know functions also act like objects in JavaScript – but did you think that they also count as garbage? Most functions aren’t a problem, since you simply create or assign them on startup and re-use them. However, functions that return other functions (such as in closures) can be more of a problem than you think. Consider the following example using requestAnimationFrame to call a game’s tick function on an object (the vendor-specific extensions and workarounds have been omitted for clarity):
Get top Black Friday deals sent straight to your inbox: Sign up now!
We curate the best offers on creative kit and give our expert recommendations to save you time this Black Friday. Upgrade your setup for less with Creative Bloq.
// BAD: create a whole new function// (which is garbage collected)// and call that for the next framerequestAnimationFrame((function (self) { return function () { self.tick(); };})(this));// BETTER: re-use the same tick function!// On startup:this.tick_function = (function (self) { return function () { self.tick(); };})(this);// When requesting the next call:requestAnimationFrame(this.tick_function);
Not heard of requestAnimationFrame before? Look it up, it’s better than setInterval or setTimeout for animations!
Anyway, notice how the first example creates a new function to call every tick. The second creates one on startup and re-uses it, which avoids throwing away a function every frame.
The same can be applied to objects. You can re-use an object by deleting its properties and adding them from scratch again, instead of assigning a new object. However, this is not generally a good idea. Modern browsers optimise for objects staying the same. Deleting properties causes many JavaScript engines to de-optimise and start running slower.
You should definitely avoid deleting properties in general. However, it’s a trade-off – if deleting properties saves enough garbage to reduce pauses, and it’s worth it for the lower execution speed in accessing that object, you might be able to get away with it. Bear in mind though that wherever possible, it’s better to re-use an object with the same properties and not delete anything.
Moving on, things become a little more difficult. Many library functions in JavaScript return new objects, which can make it difficult to avoid allocations. For example, the Array object’s slice function returns a new array based on a section of the old array. If you call slice, an allocation is made, and there’s nothing you can do about that except to avoid calling it. You can also rewrite an allocation-free version in JavaScript, but you could end up reinventing the wheel, re-implementing a lot of common JavaScript functions. It might be worth considering for hotspots of garbage creation, though.
As always the Mozilla Developer Network (MDN) is a great place to look up JavaScript library functions. For example, the MDN documentation on Array.slice states that “slice does not alter the original array, but returns a new ‘one level deep’ copy”. This is our hint that the function returns a copy (a new array) rather than modifying the original; therefore we should avoid it when minimising garbage creation. By looking up the other functions in your per-frame code path, you can also find out whether they are creating new objects or modifying in-place. They can be easy to mix up. If possible, replacing object-creating functions with ones that modify the object in-place can help reduce garbage creation. Consider the example below:
// Intention: reduce array to elements// 1, 2 and 3.// BAD: calling slice: new array is created// and old one is garbagedarr = arr.slice(1, 4);// BETTER: set length to 4, then shift// (removing first element)// Does not create any new objects!arr.length = 4;arr.shift();
Caches can also help you recycle lots of objects. For example, a particle effect may store an array of particle objects. When particles are destroyed you could just remove them from the array, where they become garbage and will later be cleaned up. A smarter choice is recycling: moving them to a ‘dead particles array’.
When creating new particles, if the ‘dead particles array’ has any items, pop() an item and reset it to its initial state, then move it back to the active particles array. If the ‘dead particles array’ is empty you have no choice but to create a new Particle(), but that’s OK; you’ll need to create new particles on startup anyway (when the array is initially empty), and from then on they’ll be recycled.
Similar principles can be applied throughout your JavaScript. For instance, you may have a stack of objects, implemented by pushing and popping to an array. If popping from the array would garbage the object, you could consider caching it. You could also use an integer pointing at the ‘top index’ in the stack, and increment and decrement that instead of pushing and popping, recycling objects as you go. Stacks can also be an important way to work around passing new objects along in recursive functions, where as useful as the {} syntax is, it still can create a lot of garbage.
Some of the worst offenders for GC performance in JavaScript are vector objects (such as an object containing an x and y co-ordinate); JS implementations of Box2D physics often suffer from this issue. Libraries designed around such objects can create thousands of vector objects per second, often with no way to recycle them – or requiring special calls to ‘free’ them, which can be tricky to get in exactly the right places.
Often applications made with libraries like these show the worst garbage collection pauses. It’s far better to have functions operating on each co-ordinate separately – such as getX() and getY(), which return simple numbers, rather than getPosition(), which returns a new vector2(…). When deciding between libraries, for best performance you probably want to avoid any that use vector objects like that. Vector objects are indeed convenient, and can make your code neater and simpler, but the JavaScript language’s design makes it hard to use them without creating huge amounts of garbage – making your game or animation choppier!
So making modifications to reduce GC overhead can, sadly, make your code more complicated and difficult to follow. But with care it’s perfectly possible to craft real-time JavaScript that will make your games, effects or animations smoother and more responsive than the rest.
Thank you for reading 5 articles this month* Join now for unlimited access
Enjoy your first month for just £1 / $1 / €1
*Read 5 free articles per month without a subscription
Join now for unlimited access
Try first month for just £1 / $1 / €1
The Creative Bloq team is made up of a group of design fans, and has changed and evolved since Creative Bloq began back in 2012. The current website team consists of eight full-time members of staff: Editor Georgia Coggan, Deputy Editor Rosie Hilder, Ecommerce Editor Beren Neale, Senior News Editor Daniel Piper, Editor, Digital Art and 3D Ian Dean, Tech Reviews Editor Erlingur Einarsson and Ecommerce Writer Beth Nicholls and Staff Writer Natalie Fear, as well as a roster of freelancers from around the world. The 3D World and ImagineFX magazine teams also pitch in, ensuring that content from 3D World and ImagineFX is represented on Creative Bloq.