Use GC.AllocateUninitializedArray in File.ReadAllBytes[Async] - #132398
Merged
Conversation
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copilot created this pull request from a session on behalf of
adamsitnik
August 17, 2026 10:46
View session
|
Azure Pipelines: Successfully started running 3 pipeline(s). 13 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
|
Tagging subscribers to this area: @dotnet/area-system-io |
Contributor
There was a problem hiding this comment.
Pull request overview
This pull request optimizes System.IO.File.ReadAllBytes and File.ReadAllBytesAsync in System.Private.CoreLib by avoiding the cost of zero-initializing the destination buffer when the subsequent read loop overwrites the entire array.
Changes:
- Switch
ReadAllBytesto allocate its result buffer viaGC.AllocateUninitializedArray<byte>(count)instead ofnew byte[count]. - Switch the async implementation path (
InternalReadAllBytesAsync) to allocate viaGC.AllocateUninitializedArray<byte>(count)as well. - Add inline comments documenting why skipping zero-init is safe due to full-buffer overwrite and EOF exception behavior.
Member
|
@EgorBot -osx_arm64 -linux_x64 -windows_x64 using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkSwitcher.FromAssembly(typeof(Benchmarks).Assembly).Run(args);
[MemoryDiagnoser]
public class Benchmarks
{
private string? _filePath;
[Params(1024, 100_000, 10_000_000)]
public int FileSize;
[GlobalSetup]
public void Setup()
{
_filePath = Path.GetTempFileName();
byte[] content = new byte[FileSize];
new Random(42).NextBytes(content);
File.WriteAllBytes(_filePath, content);
}
[GlobalCleanup]
public void Cleanup() => File.Delete(_filePath!);
[Benchmark]
public byte[] Sync() => File.ReadAllBytes(_filePath!);
[Benchmark]
public Task<byte[]> Async() => File.ReadAllBytesAsync(_filePath!);
} |
adamsitnik
marked this pull request as ready for review
August 17, 2026 12:12
|
Azure Pipelines: Successfully started running 3 pipeline(s). 13 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
EgorBo
reviewed
Aug 17, 2026
adamsitnik
approved these changes
Aug 17, 2026
Member
There was a problem hiding this comment.
LGTM. The benchmark results show a 3-6% performance improvement on Unix (macOS and Linux) for medium and large files. For small files, and on Windows in general, the difference is within the margin of error.
Details
Results for macos26_helix_arm64
BenchmarkDotNet v0.16.0-preview.1, macOS Sequoia 15.4.1 (24E263) [Darwin 24.4.0]
Apple M4, 1 CPU, 10 logical and 10 physical cores
Memory: 16 GB Total, 4.54 GB Available
.NET SDK 11.0.100-rc.1.26413.103
[Host] : .NET 11.0.0 (11.0.0-rc.1.26413.103, 11.0.26.41403), Arm64 RyuJIT armv8.0-a
| Method | Toolchain | FileSize | Mean | Error | Ratio | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|---|
| Sync | main** | 1024 | 7.471 μs | 0.0381 μs | 1.01 | 0.1297 | - | - | 1.08 KB | 1.00 |
| Sync | PR #132398 | 1024 | 7.408 μs | 0.0276 μs | 1.00 | 0.1297 | - | - | 1.08 KB | 1.00 |
| Async | main | 1024 | 9.767 μs | 0.0385 μs | 1.00 | 0.1678 | - | - | 1.38 KB | 1.00 |
| Async | PR #132398 | 1024 | 9.731 μs | 0.0509 μs | 1.00 | 0.1678 | - | - | 1.38 KB | 1.00 |
| Sync | main** | 100000 | 17.348 μs | 0.0929 μs | 1.01 | 31.2195 | 31.2195 | 31.2195 | 97.76 KB | 1.00 |
| Sync | PR #132398 | 100000 | 17.158 μs | 0.1232 μs | 1.00 | 31.2195 | 31.2195 | 31.2195 | 97.76 KB | 1.00 |
| Async | main | 100000 | 16.556 μs | 0.0899 μs | 1.04 | 31.2195 | 31.2195 | 31.2195 | 98.06 KB | 1.00 |
| Async | PR #132398 | 100000 | 15.899 μs | 0.1585 μs | 1.00 | 31.2195 | 31.2195 | 31.2195 | 98.06 KB | 1.00 |
| Sync | main** | 10000000 | 733.256 μs | 6.7370 μs | 1.03 | 166.9922 | 166.9922 | 166.9922 | 9767.1 KB | 1.00 |
| Sync | PR #132398 | 10000000 | 713.734 μs | 6.2004 μs | 1.00 | 224.6094 | 224.6094 | 224.6094 | 9767.61 KB | 1.00 |
| Async | main | 10000000 | 667.484 μs | 3.6360 μs | 1.16 | 150.3906 | 150.3906 | 150.3906 | 9767.01 KB | 1.00 |
| Async | PR #132398 | 10000000 | 575.771 μs | 7.2609 μs | 1.00 | 109.3750 | 109.3750 | 109.3750 | 9766.69 KB | 1.00 |
Results for ubuntu24_azure_turin
BenchmarkDotNet v0.16.0-preview.1, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V45 2.60GHz, 1 CPU, 8 logical and 4 physical cores
Memory: 31.34 GB Total, 10.28 GB Available
.NET SDK 11.0.100-rc.1.26413.103
[Host] : .NET 11.0.0 (11.0.0-rc.1.26413.103, 11.0.26.41403), X64 RyuJIT x86-64-v4
| Method | Toolchain | FileSize | Mean | Error | Ratio | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|---|
| Sync | main** | 1024 | 5.531 μs | 0.0499 μs | 1.01 | 0.0610 | - | - | 1.08 KB | 1.00 |
| Sync | PR #132398 | 1024 | 5.458 μs | 0.0456 μs | 1.00 | 0.0610 | - | - | 1.08 KB | 1.00 |
| Async | main | 1024 | 7.303 μs | 0.1409 μs | 0.99 | 0.0763 | - | - | 1.37 KB | 1.00 |
| Async | PR #132398 | 1024 | 7.349 μs | 0.1433 μs | 1.00 | 0.0839 | - | - | 1.38 KB | 1.00 |
| Sync | main** | 100000 | 44.881 μs | 0.3425 μs | 0.98 | 31.1890 | 31.1890 | 31.1890 | 97.76 KB | 1.00 |
| Sync | PR #132398 | 100000 | 45.630 μs | 0.5886 μs | 1.00 | 31.1890 | 31.1890 | 31.1890 | 97.76 KB | 1.00 |
| Async | main | 100000 | 12.532 μs | 0.2492 μs | 1.06 | 31.2347 | 31.2347 | 31.2347 | 98.06 KB | 1.00 |
| Async | PR #132398 | 100000 | 11.877 μs | 0.2354 μs | 1.00 | 31.2347 | 31.2347 | 31.2347 | 98.06 KB | 1.00 |
| Sync | main** | 10000000 | 608.650 μs | 5.2426 μs | 1.06 | 500.0000 | 500.0000 | 500.0000 | 9766.11 KB | 1.00 |
| Sync | PR #132398 | 10000000 | 571.953 μs | 6.8419 μs | 1.00 | 500.0000 | 500.0000 | 500.0000 | 9766.11 KB | 1.00 |
| Async | main | 10000000 | 1,144.992 μs | 4.6155 μs | 1.06 | 396.4844 | 396.4844 | 396.4844 | 9767.52 KB | 1.00 |
| Async | PR #132398 | 10000000 | 1,078.073 μs | 21.3568 μs | 1.00 | 396.4844 | 396.4844 | 396.4844 | 9769.31 KB | 1.00 |
Results for windows_azure_turin
BenchmarkDotNet v0.16.0-preview.1, Windows 11 (10.0.26100.33296/24H2/2024Update/HudsonValley) (Hyper-V)
AMD EPYC 9V45 2.60GHz, 1 CPU, 8 logical and 4 physical cores
Memory: 31.99 GB Total, 27.01 GB Available
.NET SDK 11.0.100-rc.1.26413.103
[Host] : .NET 11.0.0 (11.0.0-rc.1.26413.103, 11.0.26.41403), X64 RyuJIT x86-64-v4
| Method | Toolchain | FileSize | Mean | Error | Ratio | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|---|
| Sync | main** | 1024 | 22.55 μs | 0.364 μs | 1.02 | 0.0610 | - | - | 1.1 KB | 1.00 |
| Sync | PR #132398 | 1024 | 22.12 μs | 0.315 μs | 1.00 | 0.0610 | - | - | 1.1 KB | 1.00 |
| Async | main | 1024 | 35.10 μs | 0.669 μs | 1.01 | 0.0610 | - | - | 1.61 KB | 1.00 |
| Async | PR #132398 | 1024 | 34.66 μs | 0.456 μs | 1.00 | 0.0610 | - | - | 1.61 KB | 1.00 |
| Sync | main** | 100000 | 57.16 μs | 0.689 μs | 1.12 | 31.1890 | 31.1890 | 31.1890 | 97.78 KB | 1.00 |
| Sync | PR #132398 | 100000 | 51.09 μs | 0.581 μs | 1.00 | 31.1890 | 31.1890 | 31.1890 | 97.78 KB | 1.00 |
| Async | main | 100000 | 39.72 μs | 0.400 μs | 1.02 | 31.1890 | 31.1890 | 31.1890 | 98.29 KB | 1.00 |
| Async | PR #132398 | 100000 | 38.92 μs | 0.759 μs | 1.00 | 31.1890 | 31.1890 | 31.1890 | 98.29 KB | 1.00 |
| Sync | main** | 10000000 | 1,701.06 μs | 33.238 μs | 1.00 | 500.0000 | 500.0000 | 500.0000 | 9766.13 KB | 1.00 |
| Sync | PR #132398 | 10000000 | 1,699.38 μs | 33.987 μs | 1.00 | 500.0000 | 500.0000 | 500.0000 | 9766.13 KB | 1.00 |
| Async | main | 10000000 | 2,419.92 μs | 79.170 μs | 1.01 | 390.6250 | 390.6250 | 390.6250 | 9768 KB | 1.00 |
| Async | PR #132398 | 10000000 | 2,395.01 μs | 47.691 μs | 1.00 | 390.6250 | 390.6250 | 390.6250 | 9769.52 KB | 1.00 |
jkotas
approved these changes
Aug 17, 2026
Member
|
/ba-g networking failures, unrelated |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
File.ReadAllBytesandFile.ReadAllBytesAsyncallocate their result buffer withnew byte[count], paying for zeroing that the read loop immediately overwrites. Idea by @GrabYourPitchforks.Changes
File.ReadAllBytesandFile.InternalReadAllBytesAsyncnow allocate the result viaGC.AllocateUninitializedArray<byte>(count).Safety: both loops fill the entire buffer and throw
EndOfFileExceptionif the file yields fewer bytes than its reported length, so the array is either fully written or discarded with the exception — callers can never observe uninitialized memory.GC.AllocateUninitializedArrayfalls back to a zeroed allocation below its internal size threshold, so only large reads are affected.Note: the issue refers to
GC.GetUninitializedArray; the actual API isGC.AllocateUninitializedArray<T>.Benchmarks
BenchmarkDotNet, comparing both allocation strategies in otherwise identical
RandomAccess-based read loops (in-process toolchain, ShortRun, Linux x64, .NET 11 preview, noisy CI-class machine):10 MB reads are ~14% (sync) / ~24% (async) faster; smaller sizes fall within noise.
Note
This pull request was created by GitHub Copilot.