Skip to content

Instantly share code, notes, and snippets.

@roninjin10
Last active October 11, 2025 06:17
Show Gist options
  • Select an option

  • Save roninjin10/35cdb0afbe31594b62612754661f2208 to your computer and use it in GitHub Desktop.

Select an option

Save roninjin10/35cdb0afbe31594b62612754661f2208 to your computer and use it in GitHub Desktop.
javascript benchmarks aos vs soa
import { bench, describe } from 'vitest';
const NUM_ENTITIES = 100000;
// ============================================================================
// IMPLEMENTATION 1: Array of Structs (AoS) - Traditional OOP approach
// ============================================================================
// Using objects with MANY unused fields
const entitiesAoS_Objects = [];
for (let i = 0; i < NUM_ENTITIES; i++) {
entitiesAoS_Objects.push({
id: i, // UNUSED in calculation
x: Math.random() * 100,
y: Math.random() * 100,
z: Math.random() * 100,
vx: Math.random() * 10 - 5,
vy: Math.random() * 10 - 5,
vz: Math.random() * 10 - 5,
mass: Math.random() * 100, // UNUSED in calculation
health: Math.random() * 100, // UNUSED in calculation
team: Math.floor(Math.random() * 4), // UNUSED in calculation
lastUpdateTime: Date.now() // UNUSED in calculation
});
}
function updatePositionsAoS_Objects(entities, dt) {
for (let i = 0; i < entities.length; i++) {
const entity = entities[i];
entity.x += entity.vx * dt;
entity.y += entity.vy * dt;
entity.z += entity.vz * dt;
}
}
// ============================================================================
// IMPLEMENTATION 2: Array of Structs (AoS) - Using ES6 Classes
// ============================================================================
class Entity {
constructor(id) {
this.id = id; // UNUSED
this.x = Math.random() * 100;
this.y = Math.random() * 100;
this.z = Math.random() * 100;
this.vx = Math.random() * 10 - 5;
this.vy = Math.random() * 10 - 5;
this.vz = Math.random() * 10 - 5;
this.mass = Math.random() * 100; // UNUSED
this.health = Math.random() * 100; // UNUSED
this.team = Math.floor(Math.random() * 4); // UNUSED
this.lastUpdateTime = Date.now(); // UNUSED
}
updatePosition(dt) {
this.x += this.vx * dt;
this.y += this.vy * dt;
this.z += this.vz * dt;
}
}
const entitiesAoS_Classes = [];
for (let i = 0; i < NUM_ENTITIES; i++) {
entitiesAoS_Classes.push(new Entity(i));
}
function updatePositionsAoS_Classes(entities, dt) {
for (let i = 0; i < entities.length; i++) {
entities[i].updatePosition(dt);
}
}
// ============================================================================
// IMPLEMENTATION 3: Array of Structs (AoS) - Using typed array with interleaved data
// ============================================================================
// Each entity: [id, x, y, z, vx, vy, vz, mass, health, team, lastUpdateTime] = 11 floats
const FIELDS_PER_ENTITY = 11;
const entitiesAoS_TypedArray = new Float64Array(NUM_ENTITIES * FIELDS_PER_ENTITY);
for (let i = 0; i < NUM_ENTITIES; i++) {
const offset = i * FIELDS_PER_ENTITY;
entitiesAoS_TypedArray[offset + 0] = i; // id (UNUSED)
entitiesAoS_TypedArray[offset + 1] = Math.random() * 100; // x
entitiesAoS_TypedArray[offset + 2] = Math.random() * 100; // y
entitiesAoS_TypedArray[offset + 3] = Math.random() * 100; // z
entitiesAoS_TypedArray[offset + 4] = Math.random() * 10 - 5; // vx
entitiesAoS_TypedArray[offset + 5] = Math.random() * 10 - 5; // vy
entitiesAoS_TypedArray[offset + 6] = Math.random() * 10 - 5; // vz
entitiesAoS_TypedArray[offset + 7] = Math.random() * 100; // mass (UNUSED)
entitiesAoS_TypedArray[offset + 8] = Math.random() * 100; // health (UNUSED)
entitiesAoS_TypedArray[offset + 9] = Math.floor(Math.random() * 4); // team (UNUSED)
entitiesAoS_TypedArray[offset + 10] = Date.now(); // lastUpdateTime (UNUSED)
}
function updatePositionsAoS_TypedArray(entities, dt) {
for (let i = 0; i < NUM_ENTITIES; i++) {
const offset = i * FIELDS_PER_ENTITY;
entities[offset + 1] += entities[offset + 4] * dt; // x += vx * dt
entities[offset + 2] += entities[offset + 5] * dt; // y += vy * dt
entities[offset + 3] += entities[offset + 6] * dt; // z += vz * dt
}
}
// ============================================================================
// IMPLEMENTATION 4: Struct of Arrays (SoA) - Data-oriented approach
// ============================================================================
const entitiesSoA = {
id: new Float64Array(NUM_ENTITIES), // UNUSED - separate array
x: new Float64Array(NUM_ENTITIES),
y: new Float64Array(NUM_ENTITIES),
z: new Float64Array(NUM_ENTITIES),
vx: new Float64Array(NUM_ENTITIES),
vy: new Float64Array(NUM_ENTITIES),
vz: new Float64Array(NUM_ENTITIES),
mass: new Float64Array(NUM_ENTITIES), // UNUSED - separate array
health: new Float64Array(NUM_ENTITIES), // UNUSED - separate array
team: new Float64Array(NUM_ENTITIES), // UNUSED - separate array
lastUpdateTime: new Float64Array(NUM_ENTITIES) // UNUSED - separate array
};
for (let i = 0; i < NUM_ENTITIES; i++) {
entitiesSoA.id[i] = i;
entitiesSoA.x[i] = Math.random() * 100;
entitiesSoA.y[i] = Math.random() * 100;
entitiesSoA.z[i] = Math.random() * 100;
entitiesSoA.vx[i] = Math.random() * 10 - 5;
entitiesSoA.vy[i] = Math.random() * 10 - 5;
entitiesSoA.vz[i] = Math.random() * 10 - 5;
entitiesSoA.mass[i] = Math.random() * 100;
entitiesSoA.health[i] = Math.random() * 100;
entitiesSoA.team[i] = Math.floor(Math.random() * 4);
entitiesSoA.lastUpdateTime[i] = Date.now();
}
function updatePositionsSoA(entities, dt) {
// Destructure arrays to avoid repeated property lookups
const { x, y, z, vx, vy, vz } = entities;
// Single loop processing all dimensions at once
for (let i = 0; i < NUM_ENTITIES; i++) {
x[i] += vx[i] * dt;
y[i] += vy[i] * dt;
z[i] += vz[i] * dt;
}
}
// ============================================================================
// BENCHMARKS
// ============================================================================
const dt = 0.016; // ~60fps frame time
describe('AoS vs SoA Position Update', () => {
bench('1. Array of Structs (Objects)', () => {
updatePositionsAoS_Objects(entitiesAoS_Objects, dt);
});
bench('2. Array of Structs (Classes)', () => {
updatePositionsAoS_Classes(entitiesAoS_Classes, dt);
});
bench('3. Array of Structs (Typed Array)', () => {
updatePositionsAoS_TypedArray(entitiesAoS_TypedArray, dt);
});
bench('4. Struct of Arrays (SoA)', () => {
updatePositionsSoA(entitiesSoA, dt);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment