-
Notifications
You must be signed in to change notification settings - Fork 5
Optimize large buffer allocations with ArrayPool #78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
375f042
81a3dd6
b9e4bd0
fbe7c1d
c0c62e9
a132bff
d57680f
65c8a88
f2b8c03
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |||||||||||||||
| // ReSharper disable PartialTypeWithSinglePart | ||||||||||||||||
|
|
||||||||||||||||
| using System; | ||||||||||||||||
| using System.Buffers; | ||||||||||||||||
| using System.IO; | ||||||||||||||||
| using System.Text; | ||||||||||||||||
| using System.Threading; | ||||||||||||||||
|
|
@@ -30,18 +31,22 @@ internal static class MemberPolyfills_Net70_TextReader | |||||||||||||||
| public async Task<string> ReadToEndAsync(CancellationToken cancellationToken) | ||||||||||||||||
| { | ||||||||||||||||
| var result = new StringBuilder(); | ||||||||||||||||
| var buffer = new char[4096]; | ||||||||||||||||
| using var buffer = MemoryPool<char>.Shared.Rent(4096); | ||||||||||||||||
|
|
||||||||||||||||
| while (true) | ||||||||||||||||
| { | ||||||||||||||||
| var charsRead = await reader | ||||||||||||||||
| .ReadAsync(buffer, cancellationToken) | ||||||||||||||||
| .ReadAsync(buffer.Memory, cancellationToken) | ||||||||||||||||
| .ConfigureAwait(false); | ||||||||||||||||
|
|
||||||||||||||||
| if (charsRead <= 0) | ||||||||||||||||
| break; | ||||||||||||||||
|
|
||||||||||||||||
| result.Append(buffer, 0, charsRead); | ||||||||||||||||
| // Append char by char to avoid string allocation | ||||||||||||||||
| for (var i = 0; i < charsRead; i++) | ||||||||||||||||
| { | ||||||||||||||||
| result.Append(buffer.Memory.Span[i]); | ||||||||||||||||
| } | ||||||||||||||||
|
||||||||||||||||
| // Append char by char to avoid string allocation | |
| for (var i = 0; i < charsRead; i++) | |
| { | |
| result.Append(buffer.Memory.Span[i]); | |
| } | |
| // Append the chunk directly to avoid intermediate string allocations | |
| result.Append(buffer.Memory.Span.Slice(0, charsRead)); |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |||||
| // ReSharper disable PartialTypeWithSinglePart | ||||||
|
|
||||||
| using System; | ||||||
| using System.Buffers; | ||||||
| using System.Text; | ||||||
| using System.Diagnostics.CodeAnalysis; | ||||||
| using System.Security.Cryptography; | ||||||
|
|
@@ -46,11 +47,18 @@ public static void Shuffle<T>(Span<T> items) | |||||
| // https://learn.microsoft.com/dotnet/api/system.security.cryptography.randomnumbergenerator.gethexstring#system-security-cryptography-randomnumbergenerator-gethexstring(system-int32-system-boolean) | ||||||
| public static string GetHexString(int stringLength, bool lowercase = false) | ||||||
| { | ||||||
| var bytes = new byte[(stringLength + 1) / 2]; | ||||||
| RandomNumberGenerator.Fill(bytes); | ||||||
| var bytes = ArrayPool<byte>.Shared.Rent((stringLength + 1) / 2); | ||||||
| try | ||||||
| { | ||||||
| RandomNumberGenerator.Fill(bytes); | ||||||
|
|
||||||
| var hex = lowercase ? Convert.ToHexStringLower(bytes) : Convert.ToHexString(bytes); | ||||||
| return hex.Substring(0, stringLength); | ||||||
| var hex = lowercase ? Convert.ToHexStringLower(bytes) : Convert.ToHexString(bytes); | ||||||
| return hex.Substring(0, stringLength); | ||||||
|
||||||
| } | ||||||
| finally | ||||||
| { | ||||||
| ArrayPool<byte>.Shared.Return(bytes); | ||||||
|
||||||
| ArrayPool<byte>.Shared.Return(bytes); | |
| ArrayPool<byte>.Shared.Return(bytes, clearArray: true); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
| // ReSharper disable InconsistentNaming | ||
| // ReSharper disable PartialTypeWithSinglePart | ||
|
|
||
| using System.Buffers; | ||
| using System.Collections.Generic; | ||
| using System.IO; | ||
| using System.Text; | ||
|
|
@@ -130,19 +131,25 @@ public static async Task<string[]> ReadAllLinesAsync(string path, Encoding encod | |
| // https://learn.microsoft.com/dotnet/api/system.io.file.readalltextasync#system-io-file-readalltextasync(system-string-system-threading-cancellationtoken) | ||
| public static async Task<string> ReadAllTextAsync(string path, CancellationToken cancellationToken = default) | ||
| { | ||
| using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true); | ||
| const int bufferSize = 4096; | ||
| using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, true); | ||
| using var reader = new StreamReader(stream); | ||
|
|
||
| var content = new StringBuilder(); | ||
| var buffer = new char[4096]; | ||
| using var buffer = MemoryPool<char>.Shared.Rent(bufferSize); | ||
|
|
||
| cancellationToken.ThrowIfCancellationRequested(); | ||
|
|
||
| int charsRead; | ||
| while ((charsRead = await reader.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0) | ||
| while ((charsRead = await reader.ReadAsync(buffer.Memory, cancellationToken).ConfigureAwait(false)) > 0) | ||
| { | ||
|
||
| cancellationToken.ThrowIfCancellationRequested(); | ||
| content.Append(buffer, 0, charsRead); | ||
|
|
||
| // Append char by char to avoid string allocation | ||
| for (var i = 0; i < charsRead; i++) | ||
| { | ||
| content.Append(buffer.Memory.Span[i]); | ||
| } | ||
|
||
| } | ||
|
|
||
| return content.ToString(); | ||
|
|
@@ -151,19 +158,25 @@ public static async Task<string> ReadAllTextAsync(string path, CancellationToken | |
| // https://learn.microsoft.com/dotnet/api/system.io.file.readalltextasync#system-io-file-readalltextasync(system-string-system-text-encoding-system-threading-cancellationtoken) | ||
| public static async Task<string> ReadAllTextAsync(string path, Encoding encoding, CancellationToken cancellationToken = default) | ||
| { | ||
| using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true); | ||
| const int bufferSize = 4096; | ||
| using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, true); | ||
| using var reader = new StreamReader(stream, encoding); | ||
|
|
||
| var content = new StringBuilder(); | ||
| var buffer = new char[4096]; | ||
| using var buffer = MemoryPool<char>.Shared.Rent(bufferSize); | ||
|
|
||
| cancellationToken.ThrowIfCancellationRequested(); | ||
|
|
||
| int charsRead; | ||
| while ((charsRead = await reader.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0) | ||
| while ((charsRead = await reader.ReadAsync(buffer.Memory, cancellationToken).ConfigureAwait(false)) > 0) | ||
| { | ||
|
||
| cancellationToken.ThrowIfCancellationRequested(); | ||
| content.Append(buffer, 0, charsRead); | ||
|
|
||
| // Append char by char to avoid string allocation | ||
| for (var i = 0; i < charsRead; i++) | ||
| { | ||
| content.Append(buffer.Memory.Span[i]); | ||
| } | ||
|
||
| } | ||
|
|
||
| return content.ToString(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using TextReader.ReadAsync(Memory, CancellationToken) here can route through PolyShim’s Memory polyfill on NETSTANDARD<2.1/NETCOREAPP<2.1, which allocates a new char[] (buffer.ToArray()) per call. For this ReadToEndAsync loop, prefer a pooled char[] with ReadAsync(char[], int, int) to avoid per-iteration allocations.