Skip to content

[C#] Update RawTableIterBase.Enumerator to use ArrayPool for buffer#4385

Merged
clockwork-labs-bot merged 4 commits intomasterfrom
rekhoff/csharp-sdk-memory-allocation-fix
Feb 25, 2026
Merged

[C#] Update RawTableIterBase.Enumerator to use ArrayPool for buffer#4385
clockwork-labs-bot merged 4 commits intomasterfrom
rekhoff/csharp-sdk-memory-allocation-fix

Conversation

@rekhoff
Copy link
Contributor

@rekhoff rekhoff commented Feb 20, 2026

This is the implementation of a fix for #4093

Description of Changes

  • Updated RawTableIterBase.Enumerator to rent its scratch buffer from ArrayPool<byte>.Shared (with dynamic re-rent on BUFFER_TOO_SMALL) and return it on dispose, so iterating rows no longer allocates a fresh byte[] per step.
  • The enumerator still exposes the row bytes via ArraySegment<byte> Current, but now the underlying storage is reused across iterations rather than recreated each time.

Testing results from master:

allocated_bytes=14000
elapsed_ticks=1521829
sum=1249975000
row_count=50000
Find() TinyRecord (8 bytes payload) -> 132208 bytes allocated
Find() MediumRecord (~50 bytes payload) -> 132440 bytes allocated
Find() LargeRecord (1 KB payload) -> 134528 bytes allocated
Find() LargeRecord (10 KB payload) -> 152408 bytes allocated
Find() LargeRecord (100 KB payload) -> 336728 bytes allocated
Iter() 10 rows -> 131896 bytes allocated
Filter() iterate 20 rows -> 133288 bytes allocated
Filter() iterate 100 rows -> 135976 bytes allocated
10x consecutive Find() (TinyRecord) -> 1319120 bytes allocated

Testing results with this fix:

allocated_bytes=14000
elapsed_ticks=1504949
sum=1249975000
row_count=50000
Find() TinyRecord (8 bytes payload) -> 1096 bytes allocated
Find() MediumRecord (~50 bytes payload) -> 1280 bytes allocated
Find() LargeRecord (1 KB payload) -> 4464 bytes allocated
Find() LargeRecord (10 KB payload) -> 27464 bytes allocated
Find() LargeRecord (100 KB payload) -> 234312 bytes allocated
Iter() 10 rows -> 680 bytes allocated
Filter() iterate 20 rows -> 1872 bytes allocated
Filter() iterate 100 rows -> 3280 bytes allocated
10x consecutive Find() (TinyRecord) -> 8000 bytes allocated

API and ABI breaking changes

No API or ABI changes

Expected complexity level and risk

2 - low/moderate:
Touches the hot-path iterator that every Iter/Find/Filter call uses

Testing

  • Compiled CLI and ran regression tests locally
  • Verified iterator-based harness runs (client + module reducers) on both master and this branch, confirming allocations drop from ~131 KB per read to payload-scaled values.
  • Ensured no regressions in standard harness sanity checks (row_count=50000, sum=1249975000).

@rekhoff rekhoff self-assigned this Feb 20, 2026
@rekhoff rekhoff marked this pull request as ready for review February 21, 2026 23:02
@rekhoff rekhoff requested a review from jdetter February 24, 2026 16:26
@rekhoff rekhoff added the bug Something isn't working label Feb 24, 2026
Copy link
Contributor

@JasonAtClockwork JasonAtClockwork left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks solid, we might want to add more testing around this in the future but my small local testing matches up.

@clockwork-labs-bot clockwork-labs-bot added this pull request to the merge queue Feb 25, 2026
Merged via the queue into master with commit 6fea15f Feb 25, 2026
32 of 33 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants