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

@rexmrexm 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.

BenchmarkCasemaster allocThis PRΔ time
RenderListN=100, dictionary1,920 B0 B+0.8%
RenderListN=100, object1,920 B0 B+0.0%
RenderListN=1000, dictionary23,520 B0 B+0.4%
RenderListN=1000, object23,520 B0 B+3.1%
RenderListN=10, both0 B0 B−6.2% / +0.7%
RenderToStringclean / html31,656 / 34,960 B30,936 / 34,240 B−0.8% / +2.2%
LargeArrayN=20000959,521 B935,425 B−1.0%
LargeArrayN=400001,919,521 B1,895,425 B−9.1%
LargeArrayN=800003,839,522 B3,815,424 B+2.3%
RenderNestedall 4 casesunchangedunchanged−4.1% to +0.4%
EndToEndboth744 B744 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

rexmand 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

Copy link
Copy Markdown

@rexm
rexm merged commit 172c5f5 into masterAug 5, 2026
7 checks passed
@rexm
rexm deleted the perf/boxed-int-cache branch August 5, 2026 05:03
Sign up for freeto 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

@rexm