The same Newtonian gravity simulation, run eight different ways — same particles, same physics, same timestep — so you can watch how much of "performance" comes from data layout, algorithmic complexity, hardware SIMD, real multithreading, precision, and moving the whole loop onto the GPU (with and without shared-memory tiling).
How to read this: switching modes or the particle-count slider resets the scene to the same seeded initial conditions, so any two modes are directly comparable at a given N. "Physics step" time is the compute-only cost (what these modes are actually about); "frame" time also includes drawing the particles to screen. Particle dots shrink automatically as the count climbs, or the whole canvas would turn into a solid blob well before the interesting part of the range. "Max @ 60fps" is a toggle: click once to jump to (and keep tracking) the largest N the current mode can sustain, following you automatically every time you switch modes — drag the slider yourself to disengage it. Every mode's slider ceiling is capped separately, and for the two GPU modes that ceiling is measured on your actual hardware at page load (not guessed), since a slow-enough single GPU dispatch can make a tab look hung the same way a runaway CPU loop can.
Mode
Particles:
FPS–
Physics step–
Frame total–
Pairwise interactions/s–
Performance has dropped and stayed there — the max particle count Auto found may no longer be accurate.
–
Render technique
Dot shape
Your browser doesn't support WebGPU, so GPU mode can't run here. Try a recent Chrome/Edge, or Safari 18+.
Max particles at 60fps
Ramps each mode's particle count up until its step time crosses the 16.7ms/frame budget, then binary-searches for the boundary.
Methodology: every mode is measured against the same fixed benchmark seed, independent of the scene above. For the six CPU/CPU-multithreaded modes, only the physics step is timed — drawing to the 2D canvas isn't part of what's being compared. For the two GPU modes, the step already includes rendering (positions are read straight from the storage buffer, so compute and render aren't separable costs) — their numbers reflect compute+render together, via onSubmittedWorkDone() for real GPU-completion timing rather than just measuring how long submit() took to return. Web Workers' number includes its real per-step structured-clone copy cost. The sweep is capped at 200,000 particles to keep a full run bounded — a mode reporting "200,000+" likely goes higher, the cap just stops the search there rather than claiming that's a hard ceiling.