From b99d992e4155681d3e1519ae21707e80abdcf1f6 Mon Sep 17 00:00:00 2001 From: Matt Sherman Date: Thu, 1 Aug 2024 21:38:41 -0400 Subject: [PATCH] Configurable buffers --- Benchmarks/Program.cs | 22 +++++++++++++ Split/Extensions/SplitOn.Byte.cs | 10 +++--- Split/Extensions/SplitOn.Char.cs | 10 +++--- Split/Extensions/SplitOnAny.Byte.cs | 10 +++--- Split/Extensions/SplitOnAny.Char.cs | 10 +++--- Split/Split.Bytes.cs | 40 +++++++++++++++++++----- Split/Split.Chars.cs | 48 +++++++++++++++++++++-------- 7 files changed, 114 insertions(+), 36 deletions(-) diff --git a/Benchmarks/Program.cs b/Benchmarks/Program.cs index d4d1d2d..f62a18b 100644 --- a/Benchmarks/Program.cs +++ b/Benchmarks/Program.cs @@ -95,4 +95,26 @@ public void SplitSearchValues() // buffer.Append(token); } } + + [Benchmark] + public void SplitStream() + { + sampleStream.Seek(0, SeekOrigin.Begin); + var splits = sampleStream.SplitOn((byte)' '); + foreach (var split in splits) { } + } + + static readonly ArrayPool pool = ArrayPool.Shared; + + [Benchmark] + public void SplitStreamArrayPool() + { + var storage = pool.Rent(4096); + + sampleStream.Seek(0, SeekOrigin.Begin); + var splits = sampleStream.SplitOn((byte)' ', 2048, storage); + foreach (var split in splits) { } + + pool.Return(storage); + } } diff --git a/Split/Extensions/SplitOn.Byte.cs b/Split/Extensions/SplitOn.Byte.cs index 912887b..c72fb1c 100644 --- a/Split/Extensions/SplitOn.Byte.cs +++ b/Split/Extensions/SplitOn.Byte.cs @@ -23,9 +23,11 @@ public static partial class SplitExtensions public static Enumerator SplitOn(this ReadOnlySpan source, byte separator) => Split.Bytes(source, separator); - /// - public static StreamEnumerator SplitOn(this Stream stream, byte separator) => Split.Bytes(stream, separator); + /// + public static StreamEnumerator SplitOn(this Stream stream, byte separator, int minBufferBytes = 1024, byte[]? bufferStorage = null) + => Split.Bytes(stream, separator, minBufferBytes, bufferStorage); - /// - public static StreamEnumerator SplitOn(this Stream stream, ReadOnlySpan separator) => Split.Bytes(stream, separator); + /// + public static StreamEnumerator SplitOn(this Stream stream, ReadOnlySpan separator, int minBufferBytes = 1024, byte[]? bufferStorage = null) + => Split.Bytes(stream, separator, minBufferBytes, bufferStorage); } diff --git a/Split/Extensions/SplitOn.Char.cs b/Split/Extensions/SplitOn.Char.cs index bc0aa47..0a74c55 100644 --- a/Split/Extensions/SplitOn.Char.cs +++ b/Split/Extensions/SplitOn.Char.cs @@ -30,9 +30,11 @@ public static partial class SplitExtensions public static Enumerator SplitOn(this ReadOnlySpan source, ReadOnlySpan separator) => Split.Chars(source, separator); - /// - public static StreamEnumerator SplitOn(this TextReader reader, char separator) => Split.Chars(reader, separator); + /// + public static StreamEnumerator SplitOn(this TextReader reader, char separator, int minBufferChars = 1024, char[]? bufferStorage = null) + => Split.Chars(reader, separator, minBufferChars, bufferStorage); - /// - public static StreamEnumerator SplitOn(this TextReader reader, ReadOnlySpan separator) => Split.Chars(reader, separator); + /// + public static StreamEnumerator SplitOn(this TextReader reader, ReadOnlySpan separator, int minBufferChars = 1024, char[]? bufferStorage = null) + => Split.Chars(reader, separator, minBufferChars, bufferStorage); } diff --git a/Split/Extensions/SplitOnAny.Byte.cs b/Split/Extensions/SplitOnAny.Byte.cs index e8e9b24..40b9a54 100644 --- a/Split/Extensions/SplitOnAny.Byte.cs +++ b/Split/Extensions/SplitOnAny.Byte.cs @@ -25,9 +25,11 @@ public static partial class SplitExtensions public static Enumerator SplitOnAny(this ReadOnlySpan source, SearchValues separators) => Split.BytesAny(source, separators); - /// - public static StreamEnumerator SplitOnAny(this Stream stream, ReadOnlySpan separators) => Split.BytesAny(stream, separators); + /// + public static StreamEnumerator SplitOnAny(this Stream stream, ReadOnlySpan separators, int minBufferBytes = 1024, byte[]? bufferStorage = null) + => Split.BytesAny(stream, separators, minBufferBytes, bufferStorage); - /// - public static StreamEnumerator SplitOnAny(this Stream stream, SearchValues separators) => Split.BytesAny(stream, separators); + /// + public static StreamEnumerator SplitOnAny(this Stream stream, SearchValues separators, int minBufferBytes = 1024, byte[]? bufferStorage = null) + => Split.BytesAny(stream, separators, minBufferBytes, bufferStorage); } diff --git a/Split/Extensions/SplitOnAny.Char.cs b/Split/Extensions/SplitOnAny.Char.cs index 8f9c2bf..8732174 100644 --- a/Split/Extensions/SplitOnAny.Char.cs +++ b/Split/Extensions/SplitOnAny.Char.cs @@ -33,9 +33,11 @@ public static partial class SplitExtensions public static Enumerator SplitOnAny(this ReadOnlySpan source, SearchValues separators) => Split.CharsAny(source, separators); - /// - public static StreamEnumerator SplitOnAny(this TextReader reader, ReadOnlySpan separators) => Split.CharsAny(reader, separators); + /// + public static StreamEnumerator SplitOnAny(this TextReader reader, ReadOnlySpan separators, int minBufferChars = 1024, char[]? bufferStorage = null) + => Split.CharsAny(reader, separators, minBufferChars, bufferStorage); - /// - public static StreamEnumerator SplitOnAny(this TextReader reader, SearchValues separators) => Split.CharsAny(reader, separators); + /// + public static StreamEnumerator SplitOnAny(this TextReader reader, SearchValues separators, int minBufferChars = 1024, char[]? bufferStorage = null) + => Split.CharsAny(reader, separators, minBufferChars, bufferStorage); } diff --git a/Split/Split.Bytes.cs b/Split/Split.Bytes.cs index d070f0b..c8b0696 100644 --- a/Split/Split.Bytes.cs +++ b/Split/Split.Bytes.cs @@ -41,10 +41,16 @@ public static partial class Split /// /// The source stream of bytes. /// The byte on which to split the string. + /// Optional. The minimum number of bytes to maintain in the buffer. + /// + /// Optional. The backing array for the required buffer. If not passed, a buffer of 2 * minBufferChars will be allocated automatically. + /// You might pass your own buffer in order to manage your own allocations, such as with . + /// /// An enumerator of subslices. Use foreach. - public static StreamEnumerator Bytes(Stream stream, byte separator) + public static StreamEnumerator Bytes(Stream stream, byte separator, int minBufferBytes = 1024, byte[]? bufferStorage = null) { - var buffer = new Buffer(stream.Read, 1024); + bufferStorage ??= new byte[minBufferBytes * 2]; + var buffer = new Buffer(stream.Read, minBufferBytes, bufferStorage); return new StreamEnumerator(buffer, separator); } @@ -53,10 +59,16 @@ public static StreamEnumerator Bytes(Stream stream, byte separator) /// /// The source stream of bytes. /// The byte string on which to split the source string. + /// Optional. The minimum number of bytes to maintain in the buffer. + /// + /// Optional. The backing array for the required buffer. If not passed, a buffer of 2 * minBufferChars will be allocated automatically. + /// You might pass your own buffer in order to manage your own allocations, such as with . + /// /// An enumerator of subslices. Use foreach. - public static StreamEnumerator Bytes(Stream stream, ReadOnlySpan separator) + public static StreamEnumerator Bytes(Stream stream, ReadOnlySpan separator, int minBufferBytes = 1024, byte[]? bufferStorage = null) { - var buffer = new Buffer(stream.Read, 1024); + bufferStorage ??= new byte[minBufferBytes * 2]; + var buffer = new Buffer(stream.Read, minBufferBytes, bufferStorage); return new StreamEnumerator(buffer, separator, true); } @@ -66,10 +78,16 @@ public static StreamEnumerator Bytes(Stream stream, ReadOnlySpan sep /// /// The source stream of bytes. /// The set of bytes on which to split the source string. Any byte in the collection will cause a split. + /// Optional. The minimum number of bytes to maintain in the buffer. + /// + /// Optional. The backing array for the required buffer. If not passed, a buffer of 2 * minBufferChars will be allocated automatically. + /// You might pass your own buffer in order to manage your own allocations, such as with . + /// /// An enumerator of subslices. Use foreach. - public static StreamEnumerator BytesAny(Stream stream, ReadOnlySpan separators) + public static StreamEnumerator BytesAny(Stream stream, ReadOnlySpan separators, int minBufferBytes = 1024, byte[]? bufferStorage = null) { - var buffer = new Buffer(stream.Read, 1024); + bufferStorage ??= new byte[minBufferBytes * 2]; + var buffer = new Buffer(stream.Read, minBufferBytes, bufferStorage); return new StreamEnumerator(buffer, separators); } @@ -78,10 +96,16 @@ public static StreamEnumerator BytesAny(Stream stream, ReadOnlySpan /// /// The source stream of bytes. /// is a type optimized for searching any in a set of bytes. + /// Optional. The minimum number of bytes to maintain in the buffer. + /// + /// Optional. The backing array for the required buffer. If not passed, a buffer of 2 * minBufferChars will be allocated automatically. + /// You might pass your own buffer in order to manage your own allocations, such as with . + /// /// An enumerator of subslices. Use foreach. - public static StreamEnumerator BytesAny(Stream stream, SearchValues separators) + public static StreamEnumerator BytesAny(Stream stream, SearchValues separators, int minBufferBytes = 1024, byte[]? bufferStorage = null) { - var buffer = new Buffer(stream.Read, 1024); + bufferStorage ??= new byte[minBufferBytes * 2]; + var buffer = new Buffer(stream.Read, minBufferBytes, bufferStorage); return new StreamEnumerator(buffer, separators); } } diff --git a/Split/Split.Chars.cs b/Split/Split.Chars.cs index cd3eea2..866592d 100644 --- a/Split/Split.Chars.cs +++ b/Split/Split.Chars.cs @@ -39,48 +39,72 @@ public static partial class Split /// /// Splits an incoming reader of chars into substrings, around a single-char separator. /// - /// The source reader of chars. + /// The source reader of chars. /// The char on which to split the incoming reader. + /// Optional. The minimum number of chars to maintain in the buffer. + /// + /// Optional. The backing array for the required buffer. If not passed, a buffer of 2 * minBufferChars will be allocated automatically. + /// You might pass your own buffer in order to manage your own allocations, such as with . + /// /// An enumerator of subslices. Use foreach. - public static StreamEnumerator Chars(TextReader stream, char separator) + public static StreamEnumerator Chars(TextReader reader, char separator, int minBufferChars = 1024, char[]? bufferStorage = null) { - var buffer = new Buffer(stream.Read, 1024); + bufferStorage ??= new char[minBufferChars * 2]; + var buffer = new Buffer(reader.Read, minBufferChars, bufferStorage); return new StreamEnumerator(buffer, separator); } /// /// Split an incoming reader of chars into substrings, around a string (multi-char) separator. /// - /// The source reader of chars. + /// The source reader of chars. /// The string on which to split the source reader. + /// Optional. The minimum number of chars to maintain in the buffer. + /// + /// Optional. The backing array for the required buffer. If not passed, a buffer of 2 * minBufferChars will be allocated automatically. + /// You might pass your own buffer in order to manage your own allocations, such as with . + /// /// An enumerator of subslices. Use foreach. - public static StreamEnumerator Chars(TextReader stream, ReadOnlySpan separator) + public static StreamEnumerator Chars(TextReader reader, ReadOnlySpan separator, int minBufferChars = 1024, char[]? bufferStorage = null) { - var buffer = new Buffer(stream.Read, 1024); + bufferStorage ??= new char[minBufferChars * 2]; + var buffer = new Buffer(reader.Read, minBufferChars, bufferStorage); return new StreamEnumerator(buffer, separator, true); } /// /// Split an incoming reader of chars into substrings, around any char in an array of separators. /// - /// The source reader of chars. + /// The source reader of chars. /// The set of chars on which to split the source string. Any char in the collection will cause a split. + /// Optional. The minimum number of chars to maintain in the buffer. + /// + /// Optional. The backing array for the required buffer. If not passed, a buffer of 2 * minBufferChars will be allocated automatically. + /// You might pass your own buffer in order to manage your own allocations, such as with . + /// /// An enumerator of subslices. Use foreach. - public static StreamEnumerator CharsAny(TextReader stream, ReadOnlySpan separators) + public static StreamEnumerator CharsAny(TextReader reader, ReadOnlySpan separators, int minBufferChars = 1024, char[]? bufferStorage = null) { - var buffer = new Buffer(stream.Read, 1024); + bufferStorage ??= new char[minBufferChars * 2]; + var buffer = new Buffer(reader.Read, minBufferChars, bufferStorage); return new StreamEnumerator(buffer, separators); } /// /// Split an incoming reader of chars into substrings, around any char in . /// - /// The source reader of chars. + /// The source reader of chars. /// is a type optimized for searching any in a set of chars. + /// Optional. The minimum number of chars to maintain in the buffer. + /// + /// Optional. The backing array for the required buffer. If not passed, a buffer of 2 * minBufferChars will be allocated automatically. + /// You might pass your own buffer in order to manage your own allocations, such as with . + /// /// An enumerator of subslices. Use foreach. - public static StreamEnumerator CharsAny(TextReader stream, SearchValues separators) + public static StreamEnumerator CharsAny(TextReader reader, SearchValues separators, int minBufferChars = 1024, char[]? bufferStorage = null) { - var buffer = new Buffer(stream.Read, 1024); + bufferStorage ??= new char[minBufferChars * 2]; + var buffer = new Buffer(reader.Read, minBufferChars, bufferStorage); return new StreamEnumerator(buffer, separators); } }