-
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 7 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.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,26 @@ 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 | ||
| var span = buffer.Memory.Span; | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inline this (and apply to other places where this pattern is used)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 65c8a88 - inlined |
||
| for (var i = 0; i < charsRead; i++) | ||
| { | ||
| content.Append(span[i]); | ||
| } | ||
| } | ||
|
|
||
| return content.ToString(); | ||
|
|
@@ -151,19 +159,26 @@ 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 | ||
| var span = buffer.Memory.Span; | ||
| for (var i = 0; i < charsRead; i++) | ||
| { | ||
| content.Append(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.