Step 3 — Array Element Kinds (and an Unexplained Result)
Goal
Measure array storage specialisation, and practise reporting a result you could not fully explain.
Predict first
5,000,000-element arrays summed with an identical operation. Rank:
PACKED_SMI all small integers
PACKED_DOUBLE all doubles
PACKED_ELEMENTS 1% nulls mixed in
HOLEY_SMI 1% holes
Run
npm run arrays
Expected output:
element kind | median ms | ns/elem | vs SMI
PACKED_SMI all small integers | 7.80 | 1.560 | 1.00x
PACKED_DOUBLE all doubles | 4.70 | 0.940 | 0.60x
PACKED_ELEMENTS 1% nulls mixed in | 7.70 | 1.540 | 0.99x
HOLEY_SMI 1% holes, no strings | 8.00 | 1.600 | 1.03x
Spread across all four kinds: 1.70x
What just happened
V8 specialises array storage by content. PACKED_SMI stores tagged 31-bit integers; PACKED_DOUBLE
stores unboxed doubles; PACKED_ELEMENTS stores general tagged values. Transitions are one-way —
adding one null to a numeric array changes storage for the whole array, and removing it does not
restore the fast kind.
And PACKED_DOUBLE measured faster than PACKED_SMI, which is backwards.
This is the point of the step. The first hypothesis was that the accumulator overflowed SMI range
(summing raw i over 5M elements reaches ~1.25e13) and transitioned to a double mid-loop —
attributing to the array a cost that belonged to the sum variable. That was testable: constraining
values with i % 100 keeps the sum near 2.5e8, well inside SMI range.
The fix was applied. The ordering did not change. Hypothesis rejected.
The most plausible remaining explanation is that SMI addition carries a per-add overflow check that double addition does not. It was not investigated further, because the entire effect is ~0.6 ns/element, and Step 4 shows what that is worth.
The skill being practised
Recording "reproduced, one hypothesis tested and rejected, not pursued because the magnitude does not justify it" is a better professional output than either a confident wrong explanation or silence. It is logged as an open question, not as a finding.
Knowing when to stop investigating is the same judgment as knowing when to stop optimising. This is a safe place to practise it: the stakes are 0.6 nanoseconds.
Checkpoint
docs/verification.md Checkpoint 3 — including that reproducing the oddity is a pass.
Going deeper (optional)
If you want to settle it, node --allow-natives-syntax --print-opt-code and compare the generated
loops. If you do settle it, update docs/measured-results.md §3 and the learning log — that is a
genuine contribution to this repository.