Description
Description
Copying data is a lot faster with larger buffers. And copying data is a large use case for System.IO.Pipelines. For example, in ASP.NET Core, we plan to use PipeWriter to write files to response bodies (dotnet/aspnetcore#24851).
@brporter got us looking at the performance of using Pipes to copy files, and this once again demonstrated how crucial large buffers are. This is why System.IO.Stream's DefaultCopyBufferSize is 81920 bytes.
runtime/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs
Lines 30 to 33 in c788fe1
PipeOptions.DefaultMinimumSegmentSize is only 4096 bytes, and this leads to terrible performance when calling something like the default implementation of WriteAsync, CopyToAsync or anything that calls PipeWriter.GetMemory() or PipeWriter.GetSpan() without a sizeHint.
In my testing, copying a 2GB file went from taking 8607ms to 1771ms by increasing PipeOptions.MinimumSegmentSize from 4096 bytes to 655350 bytes. Even with the in-between Stream.DefaultCopyBufferSize value of 81920 bytes the copy time dropped to 2458ms.
You
Configuration
You can find the benchmark app at https://github.com/halter73/PipeTest/blob/master/Program.cs.
F:\dev\halter73\PipeTest [master +3 ~1 -0 !]> dotnet --info
.NET SDK (reflecting any global.json):
Version: 6.0.100-alpha.1.20472.11
Commit: e55929c5a5
Runtime Environment:
OS Name: Windows
OS Version: 10.0.20231
OS Platform: Windows
RID: win10-x64
Base Path: F:\dev\aspnet\AspNetCore\.dotnet\sdk\6.0.100-alpha.1.20472.11\
Host (useful for support):
Version: 6.0.0-alpha.1.20507.4
Commit: 4fef87c65e
.NET SDKs installed:
6.0.100-alpha.1.20472.11 [F:\dev\aspnet\AspNetCore\.dotnet\sdk]
.NET runtimes installed:
Microsoft.NETCore.App 6.0.0-alpha.1.20468.7 [F:\dev\aspnet\AspNetCore\.dotnet\shared\Microsoft.NETCore.App]
Regression?
No.
Data
Before (4096)
F:\dev\halter73\PipeTest [slice +3 ~1 -0 !]> dotnet run .\test.in .\test.out PipelinesSE
Copying .\test.in to .\test.out with method PipelinesSE...done!
GetTotalAllocatedBytes(true): [117,722,376] bytes
GetTotalMemory(false): [3,353,520] bytes
Executed for 8607ms.
After (655350)
F:\dev\halter73\PipeTest [master +3 ~1 -0 !]> dotnet run .\test.in .\test.out PipelinesSE
Copying .\test.in to .\test.out with method PipelinesSE...done!
GetTotalAllocatedBytes(true): [1,672,544] bytes
GetTotalMemory(false): [1,718,512] bytes
Executed for 1771ms.