Runtime and Performance
We use jsPerf to evaluate performance of small snippets of code across browsers and find the fastest way to perform a given operation. Unfortunately there is sometimes no single code snippet that is fastest on all browsers (and all of their versions) and so we are forced to compromise, usually based on market share.
In order to reduce the memory usage of big arrays of numbers we recommend the use of typed arrays where possible. We made a memory saving of 20 percent in one of our demos just by switching arrays holding 3D vectors to use Float32Array.
Note that typed arrays have generally better performance characteristics than standard Arrays. Most JIT compilers understand how to directly address the underlying memory used by typed arrays, and can generate extremely efficient code to access and operate on them when the data type can be correctly predicted.
The total number of objects alive at a given time has a direct effect on the cost of garbage collection. Older VMs will stop the world for seconds during collection if there are millions of active objects. This situation keeps improving and, nowadays, even millions of objects might only stall execution for hundreds of milliseconds, although this still manifests as a perceptible "skip" in a game. Garbage collection is usually triggered by either a lot of object creations in succession, or at fixed periods of time (for example the engine may invoke a full sweep every 10 seconds).
Not surprisingly, we have found it very important for performance to keep the number of objects we create as low as possible. Some of our demos or examples do not create a single object during execution of a frame.
There are several strategies we have found to be useful for reducing object count. Reusing dynamic arrays from function to function and from frame to frame (i.e. using a scratchpad) can be very effective. Also, consider converting Arrays of Objects to flat arrays with interleaved properties. In one of the games on our site, this alone reduced object count by over 75 percent and solved problems with garbage collection pauses.
In some cases, encoding information and commands in custom bytecode can be a way to trade off runtime performance for object count. For example, if storing an SVG path, maintaining a single string that contains instructions for a particular rendering shape and decoding these instructions on the fly will likely use a lot less memory and many fewer objects (although more CPU time) than unpacking the string and storing the instructions as a hierarchy of objects.
Debugging and Profiling
All browsers now provide a debugging environment embedded as part of the browser itself. The debugger tends to be hidden under a Development Tools menu option or similar.
Debugging features provided usually include:
- Traversal and inspection of the HTML tree.
- Recording and inspection of HTTP requests.
- Console logging, a read-eval-print loop for executing snippets of code.
- Debugger with support for: Breakpoints, Stack traces, Variable watching
The profilers provided by the browsers often support call-graph capture (based on a form of instrumentation which can add a noticeable overhead to the execution) and heap snapshots (with object counters, objects size and references between them). However, these features and the implementation quality can vary between the browsers.
This article has been an overview of high-end game development for HTML5, including details of the development environment and workflow. In following articles we will talk more about particular features exposed by HTML5 and related standards that are of interest to games. As well as covering specific areas of game development such as Graphics and Audio, we will give tips and recommendations for how to extract the best quality and performance across a wide range of browsers and platforms.