Skip to content

perf: drop the pool dance from MemorySliceLineSource.Concat (#119)#124

Merged
stevehansen merged 1 commit into
masterfrom
fix/concat-pool-defeat-119
May 16, 2026
Merged

perf: drop the pool dance from MemorySliceLineSource.Concat (#119)#124
stevehansen merged 1 commit into
masterfrom
fix/concat-pool-defeat-119

Conversation

@stevehansen
Copy link
Copy Markdown
Owner

Summary

  • The pre-RFC: Unify the four CsvReader read loops behind one JIT-devirtualized engine #118 ConcatenateMemory (and its post-RFC: Unify the four CsvReader read loops behind one JIT-devirtualized engine #118 successor MemorySliceLineSource.Concat) rented a buffer from CharArrayPool, copied the three input segments in, then immediately allocated a fresh char[] of exact size and copied again — defeating the pool entirely and paying two allocations + three copies where one allocation suffices.
  • Replace with string.Concat(head.Span, newLine.AsSpan(), tail.Span). Single contiguous allocation, no pool churn, no second copy. Materialized string is captured into out string? combined so it's not discarded.
  • Drop the now-unused CsvMemoryOptions field and constructor parameter on MemorySliceLineSource; CsvReader.ReadFromMemoryOptimizedImpl updated accordingly.

Resolves #119.

Allocation profile

Path Before After
Per multiline concat 1 pool rent + 1 new char[totalLength] + 3 CopyTo 1 string.Concat (one alloc, internal contiguous copy)

Net: one fewer allocation, two fewer copies, no ArrayPool overhead, on every multiline continuation step of ReadFromMemoryOptimized.

Notes

  • This was deliberately preserved verbatim by RFC: Unify the four CsvReader read loops behind one JIT-devirtualized engine #118 (approved deviation .NET Core support #4) to keep that PR scoped to consolidation. Fixed now per the follow-up.
  • MemoryReaderLineSource.Concat already used StringHelpers.Concat (which internally calls string.Concat); only MemorySliceLineSource had the anti-pattern.
  • OptimizedRowFactory.Create still ignores rawString, so the captured combined string isn't consumed downstream — but the cost is zero now that we've materialized it anyway.

Test plan

  • dotnet build clean on netstandard2.0, net8.0, net9.0
  • dotnet test — 169/169 passing (excluding the pre-existing flaky Memory_AllocationComparison GC-ratio test)
  • Existing multiline tests in Csv.Tests/EngineUnificationTests.cs cover the ReadFromMemoryOptimized multiline path; they continue to pass without modification

🤖 Generated with Claude Code

ConcatenateMemory (and its post-#118 successor MemorySliceLineSource.Concat)
rented a buffer from CharArrayPool, copied the three input segments into
it, then immediately allocated a fresh char[] of exact size, copied
everything again into that array, and returned the rented buffer to the
pool. Two allocations and three extra copies where one allocation would
have done.

Replace with `string.Concat(head.Span, newLine.AsSpan(), tail.Span)`,
which performs a single contiguous allocation (no pool churn, no second
copy). Also capture the materialized string into the `out string? combined`
parameter, matching the other ILineSource implementations -- the
OptimizedRowFactory ignores it today, but the cost of publishing it is
zero now that we've already created the string.

The unused CsvMemoryOptions field and constructor parameter on
MemorySliceLineSource are dropped; CsvReader.ReadFromMemoryOptimizedImpl
updated accordingly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request simplifies the Concat method within the MemorySliceLineSource struct by replacing manual buffer pooling and copying logic with a call to string.Concat. As a result, the CsvMemoryOptions dependency has been removed from the struct and its constructor. I have no feedback to provide as there were no review comments.

@stevehansen stevehansen merged commit 7ef74d1 into master May 16, 2026
3 checks passed
@stevehansen stevehansen deleted the fix/concat-pool-defeat-119 branch May 16, 2026 11:28
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.

ConcatenateMemory rents from CharArrayPool but allocates a fresh char[] anyway, defeating the pool

1 participant