Grid performance observations

Recorded on 2026-10-28 with Node 24.22.1, Linux, AMD EPYC 9V74 shared host. Raw samples preserve environment, source SHA-256, every timing and memory observation. The sourceCommit is the base main bfa2d713; the new Grid source was measured in the working tree and identified separately by its content hash. No engine dependency or production scheduling change is required by the benchmark.

Reproduce and interpretation

npm ci
npm run bench:grid -- --out test-results/grid.json

The harness loads the actual TypeScript using Vite SSR, closes the server, then measures synchronous Node operations. Do not run other local tests/builds during the recorded benchmark. It uses numeric cells, 2 warmups and 7 measured samples per workload/mode. Mode order rotates each round. A batch covers at least 262,144 cell operations, and results divide by repeated complete grid passes. GC runs before each batch outside timing; any GC during allocation batches remains included. This is one shared-host process, not a hardware-controlled statistical comparison or a game FPS/GPU test.

Flat/nested arrays use direct unchecked access. Grid get/set validate both integer coordinates. Direct scans use loops; Grid scans use the callback API. Therefore this compares practical API paths, not equivalent unchecked primitives. All factory modes initialize their backing allocation before assigning generated values. Callback timing and allocation/GC variation are part of these observations. The harness observes read/write results to keep work from being discarded.

Timing

Milliseconds per complete pass, median of 7. A get-set pass reads and increments every cell; scan sums every cell; fill replaces the whole grid. Ratios at tiny sizes are especially noisy. The raw file includes minimum, maximum and all samples.

Size Workload Flat Array ms Nested Array ms Grid ms
10×20 initialize-value 0.000638 0.003039 0.000794
10×20 initialize-factory 0.000803 0.003301 0.001071
10×20 get-set 0.000800 0.001228 0.004070
10×20 scan 0.000539 0.000685 0.000848
10×20 fill 0.000388 0.001363 0.000397
128×128 initialize-value 0.073352 0.049679 0.078269
128×128 initialize-factory 0.098938 0.101027 0.175055
128×128 get-set 0.068827 0.073696 0.101747
128×128 scan 0.042609 0.045800 0.065335
128×128 fill 0.026487 0.037137 0.026170
1024×1024 initialize-value 3.868920 2.224163 3.829912
1024×1024 initialize-factory 4.938969 3.604339 5.299782
1024×1024 get-set 5.859270 6.088298 9.815511
1024×1024 scan 1.626261 2.552699 3.228396
1024×1024 fill 1.265138 1.320099 1.248644

At 1,048,576 cells, Grid get/set cost more than unchecked flat access (9.816 versus 5.859ms in this observation); Grid callback scan also cost more (3.228 versus 1.626ms). Value initialization and fill were close to the flat-array paths, while nested initialization was faster in this host/run. No across-the-board speed improvement is claimed. The 1024² Grid get-set samples ranged from 7.424 to 13.952ms, demonstrating that the median is not a frame-time guarantee. Initialization, numeric/range checks, callbacks and shared-host noise should be considered when selecting a data structure. For very hot numeric kernels, application-owned direct or typed arrays remain reasonable alternatives; Grid does not publish an unsafe backing-array escape solely to improve a benchmark.

Retained JavaScript heap

Five separate forced-GC samples per size/mode. Each sample holds one array/grid in its own call frame and returns only the byte count, preventing prior-loop temporary references from corrupting the next baseline. Medium/large samples are checked to ensure the retained array was actually observed. Values below are median heapUsed deltas in bytes, not reserved physical memory.

Size Flat bytes Nested bytes Grid bytes
10×20 1,664 2,880 1,720
128×128 131,136 138,400 131,192
1024×1024 8,388,672 8,445,856 8,388,728

In this numeric-cell observation the single-array Grid retained approximately the same heap as a flat Array; nested arrays added row-container overhead. Grid still adds a small object/metadata cost, and numeric cells do not represent arbitrary object payloads. Small heap deltas are sensitive to allocator/GC noise; raw negative/noisy samples must not be interpreted as negative memory use. No native/browser/GPU memory, process RSS allocation policy, leakage, sparse data behavior or long-running game session is established. The dense cell limit is an input bound, not a memory-availability guarantee.

Scope decisions

A private flat Array keeps coordinate indexing and full traversal simple without row objects. Grid pays explicit validation rather than silently accepting bad coordinates. No stride framework, NDArray abstraction, control rights, sparse storage, custom allocator or renderer is introduced. Blockfall keeps its current row-based representation because its row filtering/splicing would not become simpler by migrating. The placement example and contracts demonstrate an independent dense-grid use instead.