Gamasutra: The Art & Business of Making Gamesspacer
Making the Move to HTML5, Part 1
View All     RSS
August 21, 2014
arrowPress Releases
August 21, 2014
PR Newswire
View All





If you enjoy reading this site, you might also want to check out these UBM Tech sites:


 
Making the Move to HTML5, Part 1

February 7, 2013 Article Start Previous Page 3 of 3
 

Runtime and Performance

JavaScript runs on a Virtual Machine with tight security controls and a limited set of APIs to interact with the external world. Recent JavaScript engines employ Just-In-Time (or JIT) compilation to generate machine code that executes much faster than the traditional interpreted bytecode of the past. Modern engines evolve at a fast pace, improving performance and reducing memory usage with every release. As one would expect, performance is not up to the level of well-written C / C++ (we found JavaScript to be 4 to 10 times slower depending on the browser) but the gap keeps closing with every new version.

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.

Memory allocation will become an issue for big games. What fits well in a console with 512 MB can take over a gigabyte in a browser. Every JavaScript object has a higher overhead compared to the C++ equivalent.

The type of objects used can also affect memory size. JavaScript engines implement numbers as either 32-bit signed integers or 64-bit floating points, and usually both take the same amount of memory (8 bytes).

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.

As mentioned above, JavaScript uses garbage collection to dispose of objects that are no longer referenced. Garbage collectors employ mark-and-sweep algorithms to detect objects that can safely be destroyed. Some also use a concept of multiple generations to optimize the allocation and deallocation of short-lived objects, and incremental algorithms may be employed to distribute the cost of marking and sweeping. This is also an area of heavy active development.

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.

The Space Ark title is an example of where we worked very closely with the developer to optimize the game using some of the ideas given here. By drastically reducing the number of dynamically created objects we were able to essentially remove garbage collection pauses during gameplay on modern JavaScript runtimes, and still maintain the visual quality of the original Xbox Live Arcade version, including character animations and particle effects.

Execution of JavaScript code can be assumed to happen in a single thread. The latest browsers do support an API to create an equivalent to multiple JavaScript processes, known as Web Workers (described in a future article) that can only communicate via messages. No direct data sharing between processes is allowed.

Since execution is essentially single threaded, if JavaScript code executes for too long without yielding, the browser may prompt the user to stop the runaway code. If the user agrees, execution of the code will stop immediately and without warning. Even if the user does not stop the code, repetitive dialog boxes warning about long running code are likely to be annoying. Therefore we recommend that long-running work be done in small increments, using the timers and intervals provided by the browser to schedule functions to be executed in the future.

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.

Debuggers may run in the same process and the same JavaScript context as the code they try to debug, which reduces their stability. We found that all debuggers will eventually crash or fail either when code complexity becomes too high for them, or when they try to show too much information about watched variables. We also suffered from timer callbacks being invoked while stepping through code, which can break certain debuggers and can be very confusing for the developer. However, these tools are extremely valuable and are improving as the browsers are developed.

Conclusion

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.


Article Start Previous Page 3 of 3

Related Jobs

Infinity Ward / Activision
Infinity Ward / Activision — woodland hills, California, United States
[08.21.14]

Build Engineer-Infinity Ward
Disney Consumer Products
Disney Consumer Products — Glendale, California, United States
[08.20.14]

Contract Game Programmer
Cloud Imperium Games
Cloud Imperium Games — Austin, Texas, United States
[08.20.14]

Lead Network Engineer
Cloud Imperium Games
Cloud Imperium Games — Santa Monica, California, United States
[08.20.14]

Animation Programmer






Comments


Keith Nemitz
profile image
"Unfortunatelly, both the Turbulenz Engine and WebGL only support OS X versions higher than 10.5. Please update your operating system"

The constant push to update software may be good for the economy, but its like superficial consumerism on steroids.

The reason I don't upgrade my Mac is, it allows me to make my games compatible with 10.5 and above. Try any of the demos at www.mousechief.com, to see what I mean. (and the same goes for Windows. They all run on XP or better, and some still run on Win'98.)

David Galeano
profile image
As our technology runs in the browser we are limited by what the browser developers decide to support, and 10.5 is not high on their list of priorities. AFAIK there are no browsers supporting WebGL by default on 10.5.

David Sena
profile image
Nice article. Pretty thorough overview of the technological side of HTML5 games.

Enjoyed particularly the example about the problems with Space Ark and how they were solved. It helps to give a more pratical perspective about the whole matter.

K Gadd
profile image
Good article. I have to disagree with the suggestion to use jsperf microbenchmarks to decide what approach is fastest, though; there are many scenarios where jsperf's crazy design will cause the microbenchmarks to not match reality. The way it's written suppresses some of the optimizations performed by JS runtimes and causes other optimizations to apply to entire benchmark runs, obscuring the actual cost of your code. It also has a rather strange approach to variance where benchmark scores with high variance (due to GC pressure/pauses, for example) are treated as equivalent, making its conclusions entirely false.

In general for performance testing in the browser, right now you really have to settle for timing things in the context where they're actually going to run. This lets you take into account the work the JIT does to optimize your code and better observe secondary effects (like GC pressure, which may not show up at all in jsperf).

Duncan Tebbs
profile image
That's a good point. We have indeed created a lot of benchmarks outside of jsperf.

The examples you gave (GC pauses and looking at the effectiveness of JIT, in particular when using Typed Arrays) are exactly the kind of things we've had to measure with our own benchmark setups.

David Galeano
profile image
Microbenchmarking using jsPerf is just another tool in the box, it is not perfect as you said, but it has its uses. If the results we get using jsPerf are inconclusive or do not correlate with performance gains in the game, then we write our own benchmarks or try different approaches, as my colleague Duncan said.

But of course, micro-optimizations are the last thing we try, our main target is improving algorithms, logic and data structures, and that is what produces the biggest gains.

Steve Fulton
profile image
This is some great stuff! Thanks.

Paul Lenoue
profile image
I just checked out Turbulenz and am impressed! Great use of HTML5. I look forward to the rest of the articles. I just hope they try to make some non-standard casual games in order to stand out from the rest.

Lennard Feddersen
profile image
Never gets past "Loading..." on my iPad.

David Galeano
profile image
Which game did you try? It is possible that it run out of memory. We have similar issues on some Android devices and we are working on reducing the amount of resources used for tablets and mobile.

Did you try the samples at http://biz.turbulenz.com/samples ?

Lennard Feddersen
profile image
Hi David, Save The Day and Polycraft. I ran Polycraft on my desktop just fine.

David Galeano
profile image
OK, thanks, we'll look into it. Until recently WebGL was not an option on mobiles and tablets, it is still in beta actually, so we were focused more on desktop support.

Kevin Long
profile image
Really nice start to this series of articles. I look forward to taking some of these ideas into my games and HTML5 classes.
Thanks.
Kevin


none
 
Comment: