The Physics of Memory (a.k.a. Can JavaScript ECS?)

birdculture1 pts0 comments

The Physics of Memory - dmurph.com<br>The Physics of Memory<br>I’ve heard that many algorithms, like sorting, are improved quite a bit when the accessed data has high memory-locality (e.g. the CPU can load all of the relevant information in it’s L1/L2 cache for processing, instead of hitting RAM). A common way to work with this is using the Entity Component System (ECS) software architecture. However, it can be difficult to achieve this memory layout for interpreted or OOP languages (Java, Python, Javscript), as they often don’t offer the developer as much control over their object memory location. My question is:

Is it possible to use an ECS-style architecture in Javascript? And for applicable operations, does that actually do better than objects + V8’s garbage collection?

To test this, I created a complex-enough benchmark of… “balls bouncing around in a box”! This follows standard 2d physics engine techniques:

A “broad-phase ” collision step (AKA “do bounding boxes intersect?”)

A “narrow-phase ” collision step (AKA “given these intersecting boxes, resolve collisions!”)

And of course, I got very carried away and ended up creating a ton of benchmark types below. Try it!

Background - What is ECS?

If you’ve spent any time in game development, you’ve probably heard of Entity Component System (ECS) . If OOP is a row-based database (where each object stores every attribute in one memory chunk), ECS is a column-based database, storing each attribute (or collection of attributes) in separate column arrays.

Put another way (and ou can probably find much better descriptions of ECS elsewhere)… instead of building complex class hierarchies like a GameEntity that inherits from MovingObject which inherits from PhysicalObject etc, ECS breaks everything down into three distinct concepts:

Entities : These are nothing more than unique integer IDs. They don’t contain any data or behavior.

Components : Plain data structures that represent individual facets of state (e.g. Position, Velocity, Color). They contain no logic. Each entity can have a dynamic set of components, from 0 to all.

Systems : Functions or loops that execute the game logic. They query entities given a specific set of components to then operate on that data.

Benchmark Choices

I went overboard and tested across five dimensions:

Language : Javascript (testing V8 engine optimizations & TypedArrays) vs. WASM (AssemblyScript for extreme memory control).

Architecture : Traditional OOP (arrays of object references) vs. ECS (cache-friendly Structure of Arrays).

Broad-phase : Spatial AVL Tree ( O(Nlog⁡N)O(N \log N)O(NlogN) balanced tree) vs. Sweep & Prune (sort on X-axis, sweep for overlaps).

Sorting Strategy (for S&P) : Tested Insertion Sort (the textbook favorite for nearly-sorted data), Hybrid Quicksort , a zero-copy Hybrid Mergesort , and JS Native Array.sort().

Motion Coherence : Wandering (smooth movement; array stays nearly sorted), Erratic (random teleporting; extreme cache thrashing), and Static (zero motion but with permanent collisions; isolates pure memory read speed with some collisions).

Anyways, let’s see how it works! This is hosted on GitHub if you want to play around with it.

Macbook M4 Results

Running all these configurations locally on my Apple M4 Pro chip (plugged in, 200 frames) creates a lot of numbers. If you want the full dataset of all 6 runs, check out the Appendix: Full Benchmark Dataset below.

To cut through information overload, let’s focus on 3 main hypotheses:

H1: Spatial Trees vs. Sweep & Prune

Contiguous 1D memory sweeps consistently outperform hierarchical 2D spatial trees, even when both are stored in flat arrays.

At 15,000 wandering entities, the flat array Sweep & Prune (WASM ECS S&P Quick) runs in 1.81 ms (a 9.02x speedup over the baseline), while the flat array spatial tree (ECS Tree) runs in 9.97 ms (only a 1.64x speedup).

Broad-phase System<br>Architecture<br>Avg Frame Time<br>99th Percentile<br>Speedup vs OOP Tree

WASM ECS S&P (Quick)<br>Flat 1D Array<br>1.809 ms<br>2.031 ms<br>9.02x

ECS (Custom SoA) S&P (Quick)<br>Flat 1D Array<br>4.668 ms<br>6.111 ms<br>3.50x

ECS Tree<br>2D Spatial BVH<br>9.970 ms<br>43.666 ms<br>1.64x

OOP Tree<br>2D Spatial BVH<br>16.321 ms<br>40.756 ms<br>1.00x

Why is the tree so much slower, even though it uses flat arrays and avoids JavaScript heap objects?

Nested Indirection : While Sweep & Prune has a single layer of index indirection (posX[indices[i]]), the AABB Tree requires nested indirection . During traversal, the CPU must look up a child index (treeLeft[idx]) and then use that to look up the node’s bounds (treeMinX[leftChild]). This non-linear jumping across arrays prevents the CPU’s hardware prefetcher from predicting the next memory address.

Branch Misprediction : The tree traversal loop is filled with conditional branches (e.g., deciding whether to descend left, right, or both based on overlap). At 15,000 moving entities, these branches are highly unpredictable, causing frequent CPU instruction pipeline flushes. Sweep &...

memory tree arrays spatial sweep array

Related Articles