From acf3d6e34dc4f60190f3677f067f8a6f4c6abaf6 Mon Sep 17 00:00:00 2001 From: Steve Hansen Date: Sat, 16 May 2026 13:21:07 +0200 Subject: [PATCH] perf: drop the pool dance from MemorySliceLineSource.Concat (#119) 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) --- Csv/CsvReader.Engine.cs | 27 +++------------------------ Csv/CsvReader.cs | 2 +- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/Csv/CsvReader.Engine.cs b/Csv/CsvReader.Engine.cs index 4cc8a4d..05fa217 100644 --- a/Csv/CsvReader.Engine.cs +++ b/Csv/CsvReader.Engine.cs @@ -107,13 +107,11 @@ public MemoryText Concat(MemoryText head, string newLine, MemoryText tail, out s internal struct MemorySliceLineSource : ILineSource { private readonly ReadOnlyMemory csv; - private readonly CsvMemoryOptions memoryOptions; private int position; - public MemorySliceLineSource(ReadOnlyMemory csv, CsvMemoryOptions memoryOptions) + public MemorySliceLineSource(ReadOnlyMemory csv) { this.csv = csv; - this.memoryOptions = memoryOptions; this.position = 0; } @@ -164,27 +162,8 @@ public bool TryReadLine(out MemoryText line, out string? lineString) public MemoryText Concat(MemoryText head, string newLine, MemoryText tail, out string? combined) { - combined = null; - - var separator = newLine.AsMemory(); - var totalLength = head.Length + separator.Length + tail.Length; - var buffer = memoryOptions.CharArrayPool.Rent(totalLength); - - try - { - var span = buffer.AsSpan(); - head.Span.CopyTo(span); - separator.Span.CopyTo(span.Slice(head.Length)); - tail.Span.CopyTo(span.Slice(head.Length + separator.Length)); - - var result = new char[totalLength]; - span.Slice(0, totalLength).CopyTo(result); - return result.AsMemory(); - } - finally - { - memoryOptions.CharArrayPool.Return(buffer); - } + combined = string.Concat(head.Span, newLine.AsSpan(), tail.Span); + return combined.AsMemory(); } } diff --git a/Csv/CsvReader.cs b/Csv/CsvReader.cs index 04d14fc..5a14f71 100644 --- a/Csv/CsvReader.cs +++ b/Csv/CsvReader.cs @@ -158,7 +158,7 @@ public static CsvBufferWriter CreateBufferWriter(ReadOnlySpan headers, c } private static IEnumerable ReadFromMemoryOptimizedImpl(ReadOnlyMemory csv, CsvOptions options, CsvMemoryOptions memoryOptions) - => Enumerate(new MemorySliceLineSource(csv, memoryOptions), new OptimizedRowFactory(memoryOptions), options); + => Enumerate(new MemorySliceLineSource(csv), new OptimizedRowFactory(memoryOptions), options); #endif