Summary
Above roughly 50 sheets, recalc_incremental is slower than a full recalc — the method whose entire purpose is to be cheaper.
Measured at 200,000 formula cells held constant, native release:
| sheets |
full recalc (warm) |
one-cell edit + incremental |
| 1 |
978 ms |
415 ms |
| 20 |
1,214 ms |
726 ms |
| 50 |
1,587 ms |
1,313 ms |
| 200 |
3,195 ms |
4,106 ms |
At 200 sheets with realistic tab names the gap widens further: 11,508 ms full versus 18,111 ms incremental.
Why
The incremental path performs the per-formula-cell sheet scan in three additional places the full path does not — snapshot_formula_values, seed_spill_sensitive_built_index and diff_against_snapshot. That is why its slope against sheet count is about 1.7× the full path's (91 ns versus 54 ns per cell × sheet).
The underlying scan is the subject of a separate issue; this one is about the consequence.
Two things to fix, and they are independent
1. The scan. Fixing it should restore the intended ordering on its own — that is the separate issue.
2. The contract. Even once the constant is fixed, nothing guarantees the incremental path stays cheaper. A method documented as an equivalent-but-cheaper alternative should not be able to cost more than the thing it replaces, silently.
Worth considering a fallback: if the dirty closure approaches the size of the whole workbook, or the incremental bookkeeping would exceed the full path's cost, run the full path instead. The result is identical by the incremental ≡ full guarantee, so a fallback is always safe — it is purely a cost decision.
That also protects against the shape where the closure legitimately is everything, which the unconditional-seeding work has shown is common on real models.
Note on reachability
recalc_incremental has no caller outside tests today; both the WebAssembly binding and the MCP call recalc(). So this is not a live customer problem — it is a blocker on ever exposing the incremental path, which is what its umbrella issue is about.
Provenance
Found by the first multi-sheet performance measurement; every prior benchmark in this repo builds a single sheet.
Summary
Above roughly 50 sheets,
recalc_incrementalis slower than a fullrecalc— the method whose entire purpose is to be cheaper.Measured at 200,000 formula cells held constant, native release:
At 200 sheets with realistic tab names the gap widens further: 11,508 ms full versus 18,111 ms incremental.
Why
The incremental path performs the per-formula-cell sheet scan in three additional places the full path does not —
snapshot_formula_values,seed_spill_sensitive_built_indexanddiff_against_snapshot. That is why its slope against sheet count is about 1.7× the full path's (91 ns versus 54 ns per cell × sheet).The underlying scan is the subject of a separate issue; this one is about the consequence.
Two things to fix, and they are independent
1. The scan. Fixing it should restore the intended ordering on its own — that is the separate issue.
2. The contract. Even once the constant is fixed, nothing guarantees the incremental path stays cheaper. A method documented as an equivalent-but-cheaper alternative should not be able to cost more than the thing it replaces, silently.
Worth considering a fallback: if the dirty closure approaches the size of the whole workbook, or the incremental bookkeeping would exceed the full path's cost, run the full path instead. The result is identical by the
incremental ≡ fullguarantee, so a fallback is always safe — it is purely a cost decision.That also protects against the shape where the closure legitimately is everything, which the unconditional-seeding work has shown is common on real models.
Note on reachability
recalc_incrementalhas no caller outside tests today; both the WebAssembly binding and the MCP callrecalc(). So this is not a live customer problem — it is a blocker on ever exposing the incremental path, which is what its umbrella issue is about.Provenance
Found by the first multi-sheet performance measurement; every prior benchmark in this repo builds a single sheet.