Skip to content

perf: enlarge BoxedValues int cache to eliminate iterator index boxing - #653

Merged
rexm merged 3 commits into
masterfrom
perf/boxed-int-cache
Aug 5, 2026
Merged

perf: enlarge BoxedValues int cache to eliminate iterator index boxing#653
rexm merged 3 commits into
masterfrom
perf/boxed-int-cache

Conversation

@rexm

@rexm rexm commented Aug 5, 2026

Copy link
Copy Markdown
Member

Summary

Follow-up to #652. Iterators box the per-item index through BoxedValues.Int on every iteration; indexes beyond the cache (previously 0–19) allocate a fresh 24-byte box each time. After #651/#652 removed the larger allocation sources, this was the dominant remaining allocation in list rendering — e.g. 23.5KB per render of a 1000-item {{#each}}, identical for object and dictionary data.

  • Enlarges the cache from 20 to 1024 boxed integers (~32KB of process-lifetime statics), covering effectively all template loops.
  • Also enables MemoryDiagnoser on the LargeArray and EndToEnd suites so allocation deltas are visible there (separate commit).

All 1840 tests pass.

Benchmarks

MediumRun (LaunchCount=1, 15 iterations), Apple M4, .NET 10. Baseline is current master @ 9ee2d48 (i.e. after #651/#652) plus the MemoryDiagnoser commit.

Benchmark Case master alloc This PR Δ time
RenderList N=100, dictionary 1,920 B 0 B +0.8%
RenderList N=100, object 1,920 B 0 B +0.0%
RenderList N=1000, dictionary 23,520 B 0 B +0.4%
RenderList N=1000, object 23,520 B 0 B +3.1%
RenderList N=10, both 0 B 0 B −6.2% / +0.7%
RenderToString clean / html 31,656 / 34,960 B 30,936 / 34,240 B −0.8% / +2.2%
LargeArray N=20000 959,521 B 935,425 B −1.0%
LargeArray N=40000 1,919,521 B 1,895,425 B −9.1%
LargeArray N=80000 3,839,522 B 3,815,424 B +2.3%
RenderNested all 4 cases unchanged unchanged −4.1% to +0.4%
EndToEnd both 744 B 744 B −3.0% / −1.5%

Time deltas are all within the ±3% cross-run noise band observed on this machine (the −9.1% and −6.2% outliers included) — this change is about allocation, not speed.

LargeArray drops exactly the 1,004 newly-cached index boxes (~24KB/render); its remaining megabytes are the List<int> values being boxed when read as object — a separate mechanism, out of scope here.

🤖 Generated with Claude Code

rexm and others added 2 commits August 5, 2026 00:28
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Iterators box the per-item index through BoxedValues.Int on every
iteration; indexes beyond the cache allocate a fresh 24-byte box each
time, which is the dominant remaining allocation in list rendering
(e.g. 23.5KB per render of a 1000-item {{#each}}). 1024 cached boxes
(~32KB of process-lifetime statics) cover effectively all template
loops.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rexm
rexm enabled auto-merge August 5, 2026 04:42
@sonarqubecloud

sonarqubecloud Bot commented Aug 5, 2026

Copy link
Copy Markdown

@rexm
rexm merged commit 172c5f5 into master Aug 5, 2026
7 checks passed
@rexm
rexm deleted the perf/boxed-int-cache branch August 5, 2026 05:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant