HTML and CSS Reference
velocity_x = particles[i+3];
velocity_y = particles[i+4];
velocity_z = particles[i+5];
particles[i+0] += deltaTime * velocity_x;
particles[i+1] += deltaTime * velocity_y;
particles[i+2] += deltaTime * velocity_z;
// continue simulation ...
The set of all particles now requires only one object, instead of six. In this case, the memory savings will be trivial
compared to using an Array per property, although in more complex cases involving a large number of independent
systems, changes of this kind can have an impact.
In our experiments, switching to a single flat Array did indeed show no significant memory difference, but gave
between a 10% and 25% speed increase depending on the browser. One explanation for both the speed increase
and the discrepancy across browsers is that using a single object in this way presents the JIT compiler with several
opportunities for optimization (which, of course, it may or may not take advantage of ).
Some JIT compilers can take a “guess” at the type of certain variables and emit fast code to directly access object
properties. In this case, the generated native code must check that incoming objects are indeed of the correct type before
enabling their fast-path. Where the data is spread across several Arrays, the type-check must be made for each object.
Furthermore, a single Array allows the code to maintain only a single index and address all data elements as fixed offsets
from that single register. Note also that memory access for our particular example will very likely be cache-coherent.
This completely flattened structure has been commonly used by Turbulenz to optimize engine and game code
for more demanding 3D games and games that must run efficiently on mobile platforms. Unfortunately, this approach
does sometimes involve sacrificing maintainability, since changes to the original Particle structure require extremely
error-prone changes across a disproportionately large amount of code. Naturally, it is important to be sure that this
extra maintenance cost is justified.
Of course, there are a variety of compromises that can be reached to balance performance with readability,
depending on how critical a given bit of code is determined to be. For example, in the Particles case in Listing 4-6, one
obvious middle ground would be to use an Array for each of position and velocity, as shown in Listing 4-7.
Listing 4-7. Splitting Data into Logical Groups
// One Array for position x,y, and z of all particles.
// particles_pos = [
// posx, posy, posz, // particle 0
// posx, posy, posz, // particle 1
// . . .
// Array for velocity x,y, and z of all particles.
// particles_vel = [
// velz, vely, velz, // particle 0
// velz, vely, velz, // particle 1
// . . .
var particles_pos = new Array(3 * numParticles);
var particles_vel = new Array(3 * numParticles);
In Listing 4-7, only two Arrays are required, but changes to the data structure (such as adding new Particle
properties) can be made without unnecessarily large code changes.