Performance and Data-Oriented Design

47.1 Data-Oriented Design (DoD)

Optimize for memory layout and cache usage, not object hierarchies.

Struct of Arrays (SoA) with std.MultiArrayList:

Use indices (u32) instead of pointers (smaller, stable across reallocation, serializable). Use tagged unions with small tags. Store booleans out of band (separate arrays or bitsets). Avoid padding (field ordering, @sizeOf checks).

The Zig compiler itself is built this way (see Andrew Kelley's "Practical Data-Oriented Design" talk).

const Particle = struct { x: f32, y: f32, vx: f32, vy: f32, alive: bool };
var particles: std.MultiArrayList(Particle) = .{};
defer particles.deinit(allocator);
try particles.append(allocator, .{ .x = 0, .y = 0, .vx = 1, .vy = 1, .alive = true });
const s = particles.slice();
const xs = s.items(.x);                     // a contiguous []f32 column
const vxs = s.items(.vx);
for (xs, vxs) |*x, vx| x.* += vx;           // cache-friendly, SIMD-friendly loop

47.2 General Performance Tips

ReleaseFast for hot code. -mcpu=native for local deployments. Choose allocators wisely (arena per request or frame, pools for fixed-size objects).

Avoid allocation in hot paths: preallocate (ensureTotalCapacity, appendAssumeCapacity), reuse buffers (clearRetainingCapacity).

Comptime-generate lookup tables. Use inline for / inline else for specialization. Use @branchHint(.unlikely) for cold paths.

SIMD with @Vector. Prefetch with @prefetch. Use std.mem.indexOfScalar (vectorized) rather than manual loops.

Batch syscalls (buffered I/O: remember to flush), memory-map large files (std.posix.mmap).

Measure with real workloads. Read the assembly for critical loops.