Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

FileStream rewrite: Use IValueTaskSource instead of TaskCompletionSource#50802

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
carlossanlop merged 19 commits intodotnet:mainfromcarlossanlop:IValueTaskSource
Apr 15, 2021
Merged

FileStream rewrite: Use IValueTaskSource instead of TaskCompletionSource#50802

carlossanlop merged 19 commits intodotnet:mainfromcarlossanlop:IValueTaskSource
Apr 15, 2021

Conversation

@carlossanlop
Copy link
Contributor

The purpose of this PR is to remove a large allocation found inFileStream.ReadAsync andFileStream.WriteAsync, which we confirmed in ourprofiling investigation around the newFileStream rewrite code.

The allocation improvement can be achieved by creating a new type, calledFileStreamValueTaskSource, that will have the same purpose asFileStreamCompletionSource, but instead of implementingTaskCompletionSource (which returns aTask to represent the asynchronous work) it implementsIValueTaskSource, which returns aValueTask.

The behavior was largely left untouched, except for the following differences:

  • Net5CompatFileStreamStrategy.Windows will be the only strategy that consumes theFileStreamCompletionSource, so it is now a private nested class.
  • One single new type implements bothIValueTaskSource (used for write) andIValueTaskSource<int> (used for read).
  • The logic inside the callback and the cancellation token registration is the same, aside for the code that needs to report exceptions or return values.
  • We had interop errors redefined in multiple places.
  • The constants used byFileStreamCompletionSource were moved toFileStreamHelpers so they could be reused by the new type.

Unit tests

Manually ran unit tests fromSystem.IO.FileSystem,System.IO,System.Runtime andSystem.Runtime.Extensions, they all passed. I made sure to run them with theNet5Compat flag turned off, to ensure my new code was executed.

Benchmarks

  • Ran theFileStream performance benchmarks against .NET 5.0, then against the code of this PR's baseline commit, and then against this PR's new code. I compared the results, and there are subtle improvements in some benchmark tests:
.NET 5.0 vs .NET 6.0 before this PR

dotnet run --project D:\performance\src\tools\ResultsComparer\ResultsComparer.csproj -c release --base 'D:\fs50' --diff 'D:\fs60_before_pr' --threshold 0.0001%

summary:
better: 39, geomean: 2.801
worse: 22, geomean: 2.098
total diff: 61

Slowerdiff/baseBase Median (ns)Diff Median (ns)Modality
Perf_FileStream.CopyToFile(fileSize: 1048576, options: None)4.93882252.574348913.28
Perf_FileStream.CopyToFileAsync(fileSize: 1048576, options: None)4.621003671.884633945.31bimodal
Perf_FileStream.WriteByte(fileSize: 1024, options: None)3.13257218.23805205.31several?
Perf_FileStream.WriteByte(fileSize: 1024, options: Asynchronous)3.13286859.38896524.31
Perf_FileStream.Write(fileSize: 1048576, userBufferSize: 4096, options: None)3.061795459.385498006.25
Perf_FileStream.CopyToFileAsync(fileSize: 1048576, options: Asynchronous)3.051868294.535689532.29
Perf_FileStream.Write(fileSize: 1024, userBufferSize: 512, options: None)2.99254148.29758897.83
Perf_FileStream.WriteAsync(fileSize: 1024, userBufferSize: 512, options: Asynchronous)2.97283860.42843052.43
Perf_FileStream.Write(fileSize: 1048576, userBufferSize: 512, options: None)2.901844293.365353701.04several?
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 4096, options: None)2.892209384.386395554.17bimodal
Perf_FileStream.WriteAsync(fileSize: 1024, userBufferSize: 512, options: None)2.65301619.83798302.63
Perf_FileStream.CopyToFileAsync(fileSize: 1024, options: Asynchronous)1.86557062.051035575.21bimodal
Perf_FileStream.CopyToFile(fileSize: 1024, options: None)1.81478821.50868866.12
Perf_FileStream.FlushAsync(fileSize: 1024, options: None)1.693479778.755891160.42several?
Perf_FileStream.CopyToFileAsync(fileSize: 1024, options: None)1.66543512.61903388.82bimodal
Perf_FileStream.WriteAsync(fileSize: 104857600, userBufferSize: 4096, options: None)1.40168291900.00235314600.00
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 512, options: Asynchronous)1.248197915.6310200393.75
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 512, options: None)1.215313143.756444323.96
Perf_FileStream.Flush(fileSize: 1024, options: None)1.183448111.254052132.81
Perf_FileStream.CopyToFile(fileSize: 104857600, options: None)1.1150055150.0055473900.00
Perf_FileStream.CopyToFileAsync(fileSize: 104857600, options: None)1.0956033900.0061052537.50
Perf_FileStream.Write(fileSize: 104857600, userBufferSize: 4096, options: None)1.03133329050.00137489800.00
Fasterbase/diffBase Median (ns)Diff Median (ns)Modality
Perf_FileStream.SeekBackward(fileSize: 1024, options: Asynchronous)115.645489541.6747470.79
Perf_FileStream.SeekForward(fileSize: 1024, options: Asynchronous)64.902811281.2543319.93
Perf_FileStream.SeekBackward(fileSize: 1024, options: None)57.822571736.4644479.08
Perf_FileStream.SeekForward(fileSize: 1024, options: None)21.29876923.2641197.72
Perf_FileStream.WriteAsync(fileSize: 104857600, userBufferSize: 4096, options: Asynchronous)5.062463834750.00486578800.00
Perf_FileStream.OpenClose(fileSize: 1024, options: None)4.94152781.6630909.09
Perf_FileStream.OpenClose(fileSize: 1024, options: Asynchronous)4.72153731.8132593.67
Perf_FileStream.Read(fileSize: 1024, userBufferSize: 4096, options: None)4.27161781.8437889.31several?
Perf_FileStream.ReadByte(fileSize: 1024, options: None)3.98165696.6841632.18
Perf_FileStream.Read(fileSize: 1024, userBufferSize: 512, options: None)3.97159526.5840152.10
Perf_FileStream.ReadByte(fileSize: 1024, options: Asynchronous)3.50200423.3657204.05
Perf_FileStream.LockUnlock(fileSize: 1024, options: None)2.59230670.3889181.93
Perf_FileStream.LockUnlock(fileSize: 1024, options: Asynchronous)2.55238920.2293543.27
Perf_FileStream.ReadAsync(fileSize: 1024, userBufferSize: 4096, options: None)2.50193629.0877357.59
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 512, options: None)2.443222202.501318286.88
Perf_FileStream.ReadAsync(fileSize: 1024, userBufferSize: 512, options: None)2.39194808.8781591.76
Perf_FileStream.ReadAsync(fileSize: 1024, userBufferSize: 4096, options: Asynchronous)2.36216916.9892096.58
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 512, options: None)2.24325116900.00145245400.00
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 4096, options: Asynchronous)2.157025697.923271982.50
Perf_FileStream.ReadAsync(fileSize: 1024, userBufferSize: 512, options: Asynchronous)2.10199647.8995019.35
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 4096, options: Asynchronous)2.04686776300.00336991400.00bimodal
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 4096, options: Asynchronous)1.9519598723.0810074393.75
Perf_FileStream.WriteAsync(fileSize: 104857600, userBufferSize: 512, options: None)1.85508393800.00274360000.00
Perf_FileStream.WriteAsync(fileSize: 104857600, userBufferSize: 512, options: Asynchronous)1.74796591200.00457934500.00bimodal
Perf_FileStream.CopyToFileAsync(fileSize: 104857600, options: Asynchronous)1.73156593600.0090313262.50
Perf_FileStream.Flush(fileSize: 1024, options: Asynchronous)1.7022492768.7513248333.33
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 512, options: Asynchronous)1.64542213800.00331399500.00
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 512, options: Asynchronous)1.615221529.173244615.63
Perf_FileStream.FlushAsync(fileSize: 1024, options: Asynchronous)1.5021953590.9114593046.88
Perf_FileStream.Read(fileSize: 1048576, userBufferSize: 4096, options: None)1.33987460.55742610.27
Perf_FileStream.Read(fileSize: 1048576, userBufferSize: 512, options: None)1.25975146.09777023.21
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 4096, options: None)1.191301925.001096436.88
Perf_FileStream.WriteAsync(fileSize: 1024, userBufferSize: 4096, options: None)1.12142000.45126451.42
Perf_FileStream.Write(fileSize: 104857600, userBufferSize: 512, options: None)1.11162435300.00146191400.00
Perf_FileStream.WriteAsync(fileSize: 1024, userBufferSize: 4096, options: Asynchronous)1.10141476.75128484.00
Perf_FileStream.Write(fileSize: 1024, userBufferSize: 4096, options: None)1.10142083.81129048.19
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 4096, options: None)1.03116486775.00113456750.00
Perf_FileStream.Read(fileSize: 104857600, userBufferSize: 4096, options: None)1.0288348175.0086894587.50
Perf_FileStream.Read(fileSize: 104857600, userBufferSize: 512, options: None)1.0192483275.0091193650.00
.NET 5.0 vs .NET 6.0 after this PR

dotnet run --project D:\performance\src\tools\ResultsComparer\ResultsComparer.csproj -c release --base 'D:\fs50' --diff 'D:\fs60_after_pr' --threshold 0.0001%
summary:
better: 36, geomean: 3.006
worse: 22, geomean: 2.080
total diff: 58

Slowerdiff/baseBase Median (ns)Diff Median (ns)Modality
Perf_FileStream.CopyToFile(fileSize: 1048576, options: None)4.92882252.574341552.34
Perf_FileStream.CopyToFileAsync(fileSize: 1048576, options: None)4.601003671.884615128.91bimodal
Perf_FileStream.WriteAsync(fileSize: 1024, userBufferSize: 512, options: Asynchronous)3.24283860.42919970.59
Perf_FileStream.Write(fileSize: 1024, userBufferSize: 512, options: None)3.08254148.29783231.25
Perf_FileStream.CopyToFileAsync(fileSize: 1048576, options: Asynchronous)3.061868294.535710579.17
Perf_FileStream.WriteByte(fileSize: 1024, options: Asynchronous)3.05286859.38874430.38
Perf_FileStream.WriteByte(fileSize: 1024, options: None)3.04257218.23780960.42
Perf_FileStream.Write(fileSize: 1048576, userBufferSize: 4096, options: None)2.971795459.385329393.75
Perf_FileStream.Write(fileSize: 1048576, userBufferSize: 512, options: None)2.951844293.365432295.83several?
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 4096, options: None)2.922209384.386445677.08
Perf_FileStream.WriteAsync(fileSize: 1024, userBufferSize: 512, options: None)2.52301619.83758761.88
Perf_FileStream.CopyToFileAsync(fileSize: 1024, options: Asynchronous)1.95557062.051087235.83
Perf_FileStream.CopyToFileAsync(fileSize: 1024, options: None)1.73543512.61937893.38bimodal
Perf_FileStream.CopyToFile(fileSize: 1024, options: None)1.66478821.50793440.46bimodal
Perf_FileStream.FlushAsync(fileSize: 1024, options: None)1.473479778.755113488.54several?
Perf_FileStream.WriteAsync(fileSize: 104857600, userBufferSize: 4096, options: None)1.32168291900.00221660000.00
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 512, options: None)1.235313143.756512252.08
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 512, options: Asynchronous)1.228197915.6310012295.65
Perf_FileStream.Flush(fileSize: 1024, options: None)1.213448111.254163660.16
Perf_FileStream.CopyToFileAsync(fileSize: 104857600, options: None)1.1356033900.0063237650.00
Perf_FileStream.CopyToFile(fileSize: 104857600, options: None)1.0950055150.0054423625.00
Perf_FileStream.Read(fileSize: 104857600, userBufferSize: 4096, options: None)1.0188348175.0088983100.00
Fasterbase/diffBase Median (ns)Diff Median (ns)Modality
Perf_FileStream.SeekBackward(fileSize: 1024, options: Asynchronous)115.505489541.6747526.62
Perf_FileStream.SeekForward(fileSize: 1024, options: Asynchronous)64.942811281.2543292.23
Perf_FileStream.SeekBackward(fileSize: 1024, options: None)57.062571736.4645070.47
Perf_FileStream.SeekForward(fileSize: 1024, options: None)21.05876923.2641661.14
Perf_FileStream.WriteAsync(fileSize: 104857600, userBufferSize: 4096, options: Asynchronous)5.362463834750.00459292800.00
Perf_FileStream.OpenClose(fileSize: 1024, options: None)4.95152781.6630850.74
Perf_FileStream.OpenClose(fileSize: 1024, options: Asynchronous)4.75153731.8132349.21
Perf_FileStream.Read(fileSize: 1024, userBufferSize: 4096, options: None)4.27161781.8437926.07several?
Perf_FileStream.Read(fileSize: 1024, userBufferSize: 512, options: None)3.98159526.5840096.31
Perf_FileStream.ReadByte(fileSize: 1024, options: None)3.96165696.6841824.65
Perf_FileStream.LockUnlock(fileSize: 1024, options: None)2.59230670.3889051.28
Perf_FileStream.LockUnlock(fileSize: 1024, options: Asynchronous)2.56238920.2293276.60
Perf_FileStream.ReadAsync(fileSize: 1024, userBufferSize: 4096, options: None)2.53193629.0876383.11
Perf_FileStream.ReadAsync(fileSize: 1024, userBufferSize: 512, options: None)2.45194808.8779416.04
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 512, options: None)2.343222202.501376330.63
Perf_FileStream.ReadAsync(fileSize: 1024, userBufferSize: 4096, options: Asynchronous)2.31216916.9893711.06
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 512, options: None)2.28325116900.00142880200.00
Perf_FileStream.ReadByte(fileSize: 1024, options: Asynchronous)2.22200423.3690245.25
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 4096, options: Asynchronous)2.167025697.923250746.88
Perf_FileStream.WriteAsync(fileSize: 104857600, userBufferSize: 512, options: None)2.11508393800.00241032100.00
Perf_FileStream.ReadAsync(fileSize: 1024, userBufferSize: 512, options: Asynchronous)2.11199647.8994675.64
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 4096, options: Asynchronous)2.06686776300.00333108950.00bimodal
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 4096, options: Asynchronous)1.9319598723.0810171781.82
Perf_FileStream.CopyToFileAsync(fileSize: 104857600, options: Asynchronous)1.74156593600.0090035450.00
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 512, options: Asynchronous)1.65542213800.00329569650.00
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 512, options: Asynchronous)1.645221529.173182177.50
Perf_FileStream.WriteAsync(fileSize: 104857600, userBufferSize: 512, options: Asynchronous)1.64796591200.00486630000.00
Perf_FileStream.Flush(fileSize: 1024, options: Asynchronous)1.5222492768.7514797453.85
Perf_FileStream.FlushAsync(fileSize: 1024, options: Asynchronous)1.5021953590.9114625460.00
Perf_FileStream.Read(fileSize: 1048576, userBufferSize: 4096, options: None)1.33987460.55742460.71
Perf_FileStream.Read(fileSize: 1048576, userBufferSize: 512, options: None)1.25975146.09781337.50
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 4096, options: None)1.201301925.001085495.19
Perf_FileStream.WriteAsync(fileSize: 1024, userBufferSize: 4096, options: Asynchronous)1.11141476.75127158.59
Perf_FileStream.Write(fileSize: 1024, userBufferSize: 4096, options: None)1.11142083.81128134.40
Perf_FileStream.WriteAsync(fileSize: 1024, userBufferSize: 4096, options: None)1.10142000.45129068.15
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 4096, options: None)1.03116486775.00112942550.00

Profiling

Ireused the same code we have in our benchmarks, but I executed it inside aconsole app thatforces the consumption of the compiled runtime code.

You'll notice the topTask allocation went away, and theFileStreamCompletionSource allocations were substituted byFileStreamValueTaskSource allocations.

ReadAsync(bufferSize: 1, userBufferSize: FourKibibytes, FileOptions.Asynchronous)

Allocations before this PR
TypeAllocations
-System.Threading.Tasks.Task<>64,001
-System.IO.MemoryFileStreamCompletionSource64,000
- System.Threading.ThreadPoolBoundHandleOverlapped64,000
- System.Threading.OverlappedData64,000
- System.SByte[]2,797
- System.String2,153
- System.Object[]952
- System.Reflection.RuntimeParameterInfo650
- System.Byte[]539
- System.Diagnostics.Tracing.ScalarTypeInfo489
- System.Func<,,>485
- System.Reflection.ParameterInfo[]281
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>251
- System.IO.AsyncWindowsFileStreamStrategy250
Allocations after this PR
TypeAllocations
-System.IO.Strategies.AsyncWindowsFileStreamStrategy.MemoryFileStreamValueTaskSource64,000
- System.Threading.ThreadPoolBoundHandleOverlapped64,000
- System.Threading.OverlappedData64,000
- System.SByte[]2,803
- System.String2,144
- System.Object[]952
- System.Reflection.RuntimeParameterInfo650
- System.Byte[]539
- System.Reflection.ParameterInfo[]281
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>251
- System.IO.Strategies.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandle250
- System.Reflection.RuntimeMethodInfo241
- System.Signature225

ReadAsync(bufferSize: 1, userBufferSize: HalfKibibyte, FileOptions.Asynchronous)

Allocations before this PR
TypeAllocations
-System.Threading.Tasks.Task<>512,001
-System.IO.MemoryFileStreamCompletionSource512,000
- System.Threading.ThreadPoolBoundHandleOverlapped512,000
- System.Threading.OverlappedData512,000
- System.SByte[]2,806
- System.String2,153
- System.Object[]957
- System.Reflection.RuntimeParameterInfo650
- System.Byte[]539
- System.Diagnostics.Tracing.ScalarTypeInfo489
- System.Func<,,>485
- System.Reflection.ParameterInfo[]281
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>251
- System.IO.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandle250
Allocations after this PR
TypeAllocations
-System.IO.Strategies.AsyncWindowsFileStreamStrategy.MemoryFileStreamValueTaskSource512,000
- System.Threading.ThreadPoolBoundHandleOverlapped512,000
- System.Threading.OverlappedData512,000
- System.SByte[]2,803
- System.String2,144
- System.Object[]964
- System.Reflection.RuntimeParameterInfo650
- System.Byte[]539
- System.Reflection.ParameterInfo[]281
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>251
- System.IO.Strategies.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandle250
- System.Reflection.RuntimeMethodInfo241
- System.Signature225
- System.RuntimeType[]178

ReadAsync(bufferSize: FourKibibytes, userBufferSize: FourKibibytes, FileOptions.Asynchronous)

Allocations before this PR
TypeAllocations
-System.Threading.Tasks.Task<>64,013
-System.IO.MemoryFileStreamCompletionSource64,000
- System.Threading.ThreadPoolBoundHandleOverlapped64,000
- System.Threading.OverlappedData64,000
- System.SByte[]2,840
- System.String2,153
- System.Object[]955
- System.Reflection.RuntimeParameterInfo650
- System.Byte[]539
- System.Diagnostics.Tracing.ScalarTypeInfo489
- System.Func<,,>485
- System.Reflection.ParameterInfo[]281
- System.IO.BufferedFileStreamStrategy252
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>251
- System.IO.AsyncWindowsFileStreamStrategy250
- System.Threading.SemaphoreSlim250
- System.Threading.ThreadPoolBoundHandle250
Allocations after this PR
TypeAllocations
-System.IO.Strategies.AsyncWindowsFileStreamStrategy.MemoryFileStreamValueTaskSource64,000
- System.Threading.ThreadPoolBoundHandleOverlapped64,000
- System.Threading.OverlappedData64,000
- System.SByte[]2,836
- System.String2,144
- System.Object[]957
- System.Reflection.RuntimeParameterInfo650
- System.Byte[]539
- System.Reflection.ParameterInfo[]281
- System.IO.Strategies.BufferedFileStreamStrategy252
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>251
- System.IO.Strategies.AsyncWindowsFileStreamStrategy250
- System.Threading.SemaphoreSlim250
- System.Threading.ThreadPoolBoundHandle250
- System.Runtime.CompilerServices.StrongBox<>250
- System.Reflection.RuntimeMethodInfo241
- System.Signature225

ReadAsync(bufferSize: FourKibibytes, userBufferSize: HalfKibibyte, FileOptions.Asynchronous)

Allocations before this PR
TypeAllocations
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>64,235
-System.Threading.Tasks.Task<>64,015
-System.IO.FileStreamCompletionSource64,000
- System.SByte[]2,916
- System.String2,155
- System.Object[]956
- System.Byte[]789
- System.Reflection.RuntimeParameterInfo650
- System.Diagnostics.Tracing.ScalarTypeInfo489
- System.Func<,,>485
- System.Reflection.ParameterInfo[]281
- System.IO.BufferedFileStreamStrategy252
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.IO.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandleOverlapped250
- System.Threading.OverlappedData250
- System.Threading.SemaphoreSlim250
- System.Threading.ThreadPoolBoundHandle250
- System.Threading.PreAllocatedOverlapped250
Allocations after this PR
TypeAllocations
-System.IO.Strategies.AsyncWindowsFileStreamStrategy.FileStreamValueTaskSource64,000
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>63,812
- System.SByte[]2,909
- System.String2,144
- System.Object[]964
- System.Byte[]789
- System.Reflection.RuntimeParameterInfo650
- System.Reflection.ParameterInfo[]281
- System.IO.Strategies.BufferedFileStreamStrategy252
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.IO.Strategies.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandleOverlapped250
- System.Threading.OverlappedData250
- System.Threading.SemaphoreSlim250
- System.Threading.ThreadPoolBoundHandle250
- System.Threading.PreAllocatedOverlapped250
- System.Runtime.CompilerServices.StrongBox<>250
- System.Reflection.RuntimeMethodInfo241
- System.Signature225

WriteAsync(bufferSize: 1, userBufferSize: FourKibibytes, FileOptions.Asynchronous)

Allocations before this PR
TypeAllocations
-System.Threading.Tasks.Task<>64,001
-System.IO.MemoryFileStreamCompletionSource64,000
- System.Threading.ThreadPoolBoundHandleOverlapped64,000
- System.Threading.OverlappedData64,000
- System.SByte[]2,798
- System.String2,153
- System.Object[]954
- System.Reflection.RuntimeParameterInfo650
- System.Byte[]539
- System.Diagnostics.Tracing.ScalarTypeInfo489
- System.Func<,,>485
- System.Reflection.ParameterInfo[]281
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>251
- System.IO.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandle250
Allocations after this PR
TypeAllocations
-System.IO.Strategies.AsyncWindowsFileStreamStrategy.MemoryFileStreamValueTaskSource64,000
- System.Threading.ThreadPoolBoundHandleOverlapped64,000
- System.Threading.OverlappedData64,000
- System.SByte[]2,790
- System.String2,144
- System.Object[]959
- System.Reflection.RuntimeParameterInfo650
- System.Byte[]539
- System.Reflection.ParameterInfo[]281
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>251
- System.IO.Strategies.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandle250
- System.Reflection.RuntimeMethodInfo241
- System.Signature225
- System.RuntimeType[]178

WriteAsync(bufferSize: 1, userBufferSize: HalfKibibyte, FileOptions.Asynchronous)

Allocations before this PR
TypeAllocations
-System.Threading.Tasks.Task<>512,001
-System.IO.MemoryFileStreamCompletionSource512,000
- System.Threading.ThreadPoolBoundHandleOverlapped512,000
- System.Threading.OverlappedData512,000
- System.SByte[]2,812
- System.String2,153
- System.Object[]959
- System.Reflection.RuntimeParameterInfo650
- System.Byte[]539
- System.Diagnostics.Tracing.ScalarTypeInfo489
- System.Func<,,>485
- System.Reflection.ParameterInfo[]281
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>251
- System.IO.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandle250
Allocations after this PR
TypeAllocations
-System.IO.Strategies.AsyncWindowsFileStreamStrategy.MemoryFileStreamValueTaskSource512,000
- System.Threading.ThreadPoolBoundHandleOverlapped512,000
- System.Threading.OverlappedData512,000
- System.SByte[]2,790
- System.String2,144
- System.Object[]958
- System.Reflection.RuntimeParameterInfo650
- System.Byte[]539
- System.Reflection.ParameterInfo[]281
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>251
- System.IO.Strategies.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandle250
- System.Reflection.RuntimeMethodInfo241
- System.Signature225
- System.RuntimeType[]178

WriteAsync(bufferSize: FourKibibytes, userBufferSize: FourKibibytes, FileOptions.Asynchronous)

Allocations before this PR
TypeAllocations
-System.Threading.Tasks.Task<>64,014
- System.Threading.ThreadPoolBoundHandleOverlapped32,250
- System.Threading.OverlappedData32,250
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>32,249
-System.IO.MemoryFileStreamCompletionSource32,000
-System.IO.FileStreamCompletionSource32,000
- System.SByte[]2,915
- System.String2,155
- System.Object[]952
- System.Byte[]789
- System.Reflection.RuntimeParameterInfo650
- System.Diagnostics.Tracing.ScalarTypeInfo489
- System.Func<,,>485
- System.Reflection.ParameterInfo[]281
- System.IO.BufferedFileStreamStrategy252
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.IO.AsyncWindowsFileStreamStrategy250
- System.Threading.SemaphoreSlim250
- System.Threading.ThreadPoolBoundHandle250
- System.Threading.PreAllocatedOverlapped250
Allocations after this PR
TypeAllocations
- System.Threading.ThreadPoolBoundHandleOverlapped32,250
- System.Threading.OverlappedData32,250
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>32,246
-System.IO.Strategies.AsyncWindowsFileStreamStrategy.MemoryFileStreamValueTaskSource32,000
-System.IO.Strategies.AsyncWindowsFileStreamStrategy.FileStreamValueTaskSource32,000
- System.SByte[]2,893
- System.String2,144
- System.Object[]954
- System.Byte[]789
- System.Reflection.RuntimeParameterInfo650
- System.Reflection.ParameterInfo[]281
- System.IO.Strategies.BufferedFileStreamStrategy252
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.IO.Strategies.AsyncWindowsFileStreamStrategy250
- System.Threading.SemaphoreSlim250
- System.Threading.ThreadPoolBoundHandle250
- System.Threading.PreAllocatedOverlapped250
- System.Runtime.CompilerServices.StrongBox<>250
- System.Reflection.RuntimeMethodInfo241
- System.Signature225

WriteAsync(bufferSize: FourKibibytes, userBufferSize: HalfKibibyte, FileOptions.Asynchronous)

Allocations before this PR
TypeAllocations
-System.Threading.Tasks.Task<>64,014
-System.IO.FileStreamCompletionSource64,000
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>63,963
- System.SByte[]2,915
- System.String2,155
- System.Object[]956
- System.Byte[]789
- System.Reflection.RuntimeParameterInfo650
- System.Diagnostics.Tracing.ScalarTypeInfo489
- System.Func<,,>485
- System.Reflection.ParameterInfo[]281
- System.IO.BufferedFileStreamStrategy252
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.IO.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandleOverlapped250
- System.Threading.OverlappedData250
- System.Threading.SemaphoreSlim250
- System.Threading.ThreadPoolBoundHandle250
- System.Threading.PreAllocatedOverlapped250
Allocations after this PR
TypeAllocations
-System.IO.Strategies.AsyncWindowsFileStreamStrategy.FileStreamValueTaskSource64,000
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>63,830
- System.SByte[]2,912
- System.String2,144
- System.Object[]966
- System.Byte[]789
- System.Reflection.RuntimeParameterInfo650
- System.Reflection.ParameterInfo[]281
- System.IO.Strategies.BufferedFileStreamStrategy252
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.IO.Strategies.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandleOverlapped250
- System.Threading.OverlappedData250
- System.Threading.SemaphoreSlim250
- System.Threading.ThreadPoolBoundHandle250
- System.Threading.PreAllocatedOverlapped250
- System.Runtime.CompilerServices.StrongBox<>250
- System.Threading.Tasks.ValueTask.ValueTaskSourceAsTask249
- System.Threading.QueueUserWorkItemCallbackDefaultContext<>249

@adamsitnik@jozkee@jeffhandley@stephentoub

Also: Don't forget to ignore whitespace changes so it's easier to review the nested type's changes 😃

scalablecory and FilipToth reacted with heart emoji
@ghost
Copy link

Tagging subscribers to this area:@carlossanlop
See info inarea-owners.md if you want to be subscribed.

Issue Details

The purpose of this PR is to remove a large allocation found inFileStream.ReadAsync andFileStream.WriteAsync, which we confirmed in ourprofiling investigation around the newFileStream rewrite code.

The allocation improvement can be achieved by creating a new type, calledFileStreamValueTaskSource, that will have the same purpose asFileStreamCompletionSource, but instead of implementingTaskCompletionSource (which returns aTask to represent the asynchronous work) it implementsIValueTaskSource, which returns aValueTask.

The behavior was largely left untouched, except for the following differences:

  • Net5CompatFileStreamStrategy.Windows will be the only strategy that consumes theFileStreamCompletionSource, so it is now a private nested class.
  • One single new type implements bothIValueTaskSource (used for write) andIValueTaskSource<int> (used for read).
  • The logic inside the callback and the cancellation token registration is the same, aside for the code that needs to report exceptions or return values.
  • We had interop errors redefined in multiple places.
  • The constants used byFileStreamCompletionSource were moved toFileStreamHelpers so they could be reused by the new type.

Unit tests

Manually ran unit tests fromSystem.IO.FileSystem,System.IO,System.Runtime andSystem.Runtime.Extensions, they all passed. I made sure to run them with theNet5Compat flag turned off, to ensure my new code was executed.

Benchmarks

  • Ran theFileStream performance benchmarks against .NET 5.0, then against the code of this PR's baseline commit, and then against this PR's new code. I compared the results, and there are subtle improvements in some benchmark tests:
.NET 5.0 vs .NET 6.0 before this PR

dotnet run --project D:\performance\src\tools\ResultsComparer\ResultsComparer.csproj -c release --base 'D:\fs50' --diff 'D:\fs60_before_pr' --threshold 0.0001%

summary:
better: 39, geomean: 2.801
worse: 22, geomean: 2.098
total diff: 61

Slowerdiff/baseBase Median (ns)Diff Median (ns)Modality
Perf_FileStream.CopyToFile(fileSize: 1048576, options: None)4.93882252.574348913.28
Perf_FileStream.CopyToFileAsync(fileSize: 1048576, options: None)4.621003671.884633945.31bimodal
Perf_FileStream.WriteByte(fileSize: 1024, options: None)3.13257218.23805205.31several?
Perf_FileStream.WriteByte(fileSize: 1024, options: Asynchronous)3.13286859.38896524.31
Perf_FileStream.Write(fileSize: 1048576, userBufferSize: 4096, options: None)3.061795459.385498006.25
Perf_FileStream.CopyToFileAsync(fileSize: 1048576, options: Asynchronous)3.051868294.535689532.29
Perf_FileStream.Write(fileSize: 1024, userBufferSize: 512, options: None)2.99254148.29758897.83
Perf_FileStream.WriteAsync(fileSize: 1024, userBufferSize: 512, options: Asynchronous)2.97283860.42843052.43
Perf_FileStream.Write(fileSize: 1048576, userBufferSize: 512, options: None)2.901844293.365353701.04several?
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 4096, options: None)2.892209384.386395554.17bimodal
Perf_FileStream.WriteAsync(fileSize: 1024, userBufferSize: 512, options: None)2.65301619.83798302.63
Perf_FileStream.CopyToFileAsync(fileSize: 1024, options: Asynchronous)1.86557062.051035575.21bimodal
Perf_FileStream.CopyToFile(fileSize: 1024, options: None)1.81478821.50868866.12
Perf_FileStream.FlushAsync(fileSize: 1024, options: None)1.693479778.755891160.42several?
Perf_FileStream.CopyToFileAsync(fileSize: 1024, options: None)1.66543512.61903388.82bimodal
Perf_FileStream.WriteAsync(fileSize: 104857600, userBufferSize: 4096, options: None)1.40168291900.00235314600.00
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 512, options: Asynchronous)1.248197915.6310200393.75
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 512, options: None)1.215313143.756444323.96
Perf_FileStream.Flush(fileSize: 1024, options: None)1.183448111.254052132.81
Perf_FileStream.CopyToFile(fileSize: 104857600, options: None)1.1150055150.0055473900.00
Perf_FileStream.CopyToFileAsync(fileSize: 104857600, options: None)1.0956033900.0061052537.50
Perf_FileStream.Write(fileSize: 104857600, userBufferSize: 4096, options: None)1.03133329050.00137489800.00
Fasterbase/diffBase Median (ns)Diff Median (ns)Modality
Perf_FileStream.SeekBackward(fileSize: 1024, options: Asynchronous)115.645489541.6747470.79
Perf_FileStream.SeekForward(fileSize: 1024, options: Asynchronous)64.902811281.2543319.93
Perf_FileStream.SeekBackward(fileSize: 1024, options: None)57.822571736.4644479.08
Perf_FileStream.SeekForward(fileSize: 1024, options: None)21.29876923.2641197.72
Perf_FileStream.WriteAsync(fileSize: 104857600, userBufferSize: 4096, options: Asynchronous)5.062463834750.00486578800.00
Perf_FileStream.OpenClose(fileSize: 1024, options: None)4.94152781.6630909.09
Perf_FileStream.OpenClose(fileSize: 1024, options: Asynchronous)4.72153731.8132593.67
Perf_FileStream.Read(fileSize: 1024, userBufferSize: 4096, options: None)4.27161781.8437889.31several?
Perf_FileStream.ReadByte(fileSize: 1024, options: None)3.98165696.6841632.18
Perf_FileStream.Read(fileSize: 1024, userBufferSize: 512, options: None)3.97159526.5840152.10
Perf_FileStream.ReadByte(fileSize: 1024, options: Asynchronous)3.50200423.3657204.05
Perf_FileStream.LockUnlock(fileSize: 1024, options: None)2.59230670.3889181.93
Perf_FileStream.LockUnlock(fileSize: 1024, options: Asynchronous)2.55238920.2293543.27
Perf_FileStream.ReadAsync(fileSize: 1024, userBufferSize: 4096, options: None)2.50193629.0877357.59
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 512, options: None)2.443222202.501318286.88
Perf_FileStream.ReadAsync(fileSize: 1024, userBufferSize: 512, options: None)2.39194808.8781591.76
Perf_FileStream.ReadAsync(fileSize: 1024, userBufferSize: 4096, options: Asynchronous)2.36216916.9892096.58
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 512, options: None)2.24325116900.00145245400.00
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 4096, options: Asynchronous)2.157025697.923271982.50
Perf_FileStream.ReadAsync(fileSize: 1024, userBufferSize: 512, options: Asynchronous)2.10199647.8995019.35
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 4096, options: Asynchronous)2.04686776300.00336991400.00bimodal
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 4096, options: Asynchronous)1.9519598723.0810074393.75
Perf_FileStream.WriteAsync(fileSize: 104857600, userBufferSize: 512, options: None)1.85508393800.00274360000.00
Perf_FileStream.WriteAsync(fileSize: 104857600, userBufferSize: 512, options: Asynchronous)1.74796591200.00457934500.00bimodal
Perf_FileStream.CopyToFileAsync(fileSize: 104857600, options: Asynchronous)1.73156593600.0090313262.50
Perf_FileStream.Flush(fileSize: 1024, options: Asynchronous)1.7022492768.7513248333.33
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 512, options: Asynchronous)1.64542213800.00331399500.00
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 512, options: Asynchronous)1.615221529.173244615.63
Perf_FileStream.FlushAsync(fileSize: 1024, options: Asynchronous)1.5021953590.9114593046.88
Perf_FileStream.Read(fileSize: 1048576, userBufferSize: 4096, options: None)1.33987460.55742610.27
Perf_FileStream.Read(fileSize: 1048576, userBufferSize: 512, options: None)1.25975146.09777023.21
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 4096, options: None)1.191301925.001096436.88
Perf_FileStream.WriteAsync(fileSize: 1024, userBufferSize: 4096, options: None)1.12142000.45126451.42
Perf_FileStream.Write(fileSize: 104857600, userBufferSize: 512, options: None)1.11162435300.00146191400.00
Perf_FileStream.WriteAsync(fileSize: 1024, userBufferSize: 4096, options: Asynchronous)1.10141476.75128484.00
Perf_FileStream.Write(fileSize: 1024, userBufferSize: 4096, options: None)1.10142083.81129048.19
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 4096, options: None)1.03116486775.00113456750.00
Perf_FileStream.Read(fileSize: 104857600, userBufferSize: 4096, options: None)1.0288348175.0086894587.50
Perf_FileStream.Read(fileSize: 104857600, userBufferSize: 512, options: None)1.0192483275.0091193650.00
.NET 5.0 vs .NET 6.0 after this PR

dotnet run --project D:\performance\src\tools\ResultsComparer\ResultsComparer.csproj -c release --base 'D:\fs50' --diff 'D:\fs60_after_pr' --threshold 0.0001%
summary:
better: 36, geomean: 3.006
worse: 22, geomean: 2.080
total diff: 58

Slowerdiff/baseBase Median (ns)Diff Median (ns)Modality
Perf_FileStream.CopyToFile(fileSize: 1048576, options: None)4.92882252.574341552.34
Perf_FileStream.CopyToFileAsync(fileSize: 1048576, options: None)4.601003671.884615128.91bimodal
Perf_FileStream.WriteAsync(fileSize: 1024, userBufferSize: 512, options: Asynchronous)3.24283860.42919970.59
Perf_FileStream.Write(fileSize: 1024, userBufferSize: 512, options: None)3.08254148.29783231.25
Perf_FileStream.CopyToFileAsync(fileSize: 1048576, options: Asynchronous)3.061868294.535710579.17
Perf_FileStream.WriteByte(fileSize: 1024, options: Asynchronous)3.05286859.38874430.38
Perf_FileStream.WriteByte(fileSize: 1024, options: None)3.04257218.23780960.42
Perf_FileStream.Write(fileSize: 1048576, userBufferSize: 4096, options: None)2.971795459.385329393.75
Perf_FileStream.Write(fileSize: 1048576, userBufferSize: 512, options: None)2.951844293.365432295.83several?
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 4096, options: None)2.922209384.386445677.08
Perf_FileStream.WriteAsync(fileSize: 1024, userBufferSize: 512, options: None)2.52301619.83758761.88
Perf_FileStream.CopyToFileAsync(fileSize: 1024, options: Asynchronous)1.95557062.051087235.83
Perf_FileStream.CopyToFileAsync(fileSize: 1024, options: None)1.73543512.61937893.38bimodal
Perf_FileStream.CopyToFile(fileSize: 1024, options: None)1.66478821.50793440.46bimodal
Perf_FileStream.FlushAsync(fileSize: 1024, options: None)1.473479778.755113488.54several?
Perf_FileStream.WriteAsync(fileSize: 104857600, userBufferSize: 4096, options: None)1.32168291900.00221660000.00
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 512, options: None)1.235313143.756512252.08
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 512, options: Asynchronous)1.228197915.6310012295.65
Perf_FileStream.Flush(fileSize: 1024, options: None)1.213448111.254163660.16
Perf_FileStream.CopyToFileAsync(fileSize: 104857600, options: None)1.1356033900.0063237650.00
Perf_FileStream.CopyToFile(fileSize: 104857600, options: None)1.0950055150.0054423625.00
Perf_FileStream.Read(fileSize: 104857600, userBufferSize: 4096, options: None)1.0188348175.0088983100.00
Fasterbase/diffBase Median (ns)Diff Median (ns)Modality
Perf_FileStream.SeekBackward(fileSize: 1024, options: Asynchronous)115.505489541.6747526.62
Perf_FileStream.SeekForward(fileSize: 1024, options: Asynchronous)64.942811281.2543292.23
Perf_FileStream.SeekBackward(fileSize: 1024, options: None)57.062571736.4645070.47
Perf_FileStream.SeekForward(fileSize: 1024, options: None)21.05876923.2641661.14
Perf_FileStream.WriteAsync(fileSize: 104857600, userBufferSize: 4096, options: Asynchronous)5.362463834750.00459292800.00
Perf_FileStream.OpenClose(fileSize: 1024, options: None)4.95152781.6630850.74
Perf_FileStream.OpenClose(fileSize: 1024, options: Asynchronous)4.75153731.8132349.21
Perf_FileStream.Read(fileSize: 1024, userBufferSize: 4096, options: None)4.27161781.8437926.07several?
Perf_FileStream.Read(fileSize: 1024, userBufferSize: 512, options: None)3.98159526.5840096.31
Perf_FileStream.ReadByte(fileSize: 1024, options: None)3.96165696.6841824.65
Perf_FileStream.LockUnlock(fileSize: 1024, options: None)2.59230670.3889051.28
Perf_FileStream.LockUnlock(fileSize: 1024, options: Asynchronous)2.56238920.2293276.60
Perf_FileStream.ReadAsync(fileSize: 1024, userBufferSize: 4096, options: None)2.53193629.0876383.11
Perf_FileStream.ReadAsync(fileSize: 1024, userBufferSize: 512, options: None)2.45194808.8779416.04
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 512, options: None)2.343222202.501376330.63
Perf_FileStream.ReadAsync(fileSize: 1024, userBufferSize: 4096, options: Asynchronous)2.31216916.9893711.06
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 512, options: None)2.28325116900.00142880200.00
Perf_FileStream.ReadByte(fileSize: 1024, options: Asynchronous)2.22200423.3690245.25
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 4096, options: Asynchronous)2.167025697.923250746.88
Perf_FileStream.WriteAsync(fileSize: 104857600, userBufferSize: 512, options: None)2.11508393800.00241032100.00
Perf_FileStream.ReadAsync(fileSize: 1024, userBufferSize: 512, options: Asynchronous)2.11199647.8994675.64
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 4096, options: Asynchronous)2.06686776300.00333108950.00bimodal
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 4096, options: Asynchronous)1.9319598723.0810171781.82
Perf_FileStream.CopyToFileAsync(fileSize: 104857600, options: Asynchronous)1.74156593600.0090035450.00
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 512, options: Asynchronous)1.65542213800.00329569650.00
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 512, options: Asynchronous)1.645221529.173182177.50
Perf_FileStream.WriteAsync(fileSize: 104857600, userBufferSize: 512, options: Asynchronous)1.64796591200.00486630000.00
Perf_FileStream.Flush(fileSize: 1024, options: Asynchronous)1.5222492768.7514797453.85
Perf_FileStream.FlushAsync(fileSize: 1024, options: Asynchronous)1.5021953590.9114625460.00
Perf_FileStream.Read(fileSize: 1048576, userBufferSize: 4096, options: None)1.33987460.55742460.71
Perf_FileStream.Read(fileSize: 1048576, userBufferSize: 512, options: None)1.25975146.09781337.50
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 4096, options: None)1.201301925.001085495.19
Perf_FileStream.WriteAsync(fileSize: 1024, userBufferSize: 4096, options: Asynchronous)1.11141476.75127158.59
Perf_FileStream.Write(fileSize: 1024, userBufferSize: 4096, options: None)1.11142083.81128134.40
Perf_FileStream.WriteAsync(fileSize: 1024, userBufferSize: 4096, options: None)1.10142000.45129068.15
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 4096, options: None)1.03116486775.00112942550.00

Profiling

Ireused the same code we have in our benchmarks, but I executed it inside aconsole app thatforces the consumption of the compiled runtime code.

You'll notice the topTask allocation went away, and theFileStreamCompletionSource allocations were substituted byFileStreamValueTaskSource allocations.

ReadAsync(bufferSize: 1, userBufferSize: FourKibibytes, FileOptions.Asynchronous)

Allocations before this PR
TypeAllocations
-System.Threading.Tasks.Task<>64,001
-System.IO.MemoryFileStreamCompletionSource64,000
- System.Threading.ThreadPoolBoundHandleOverlapped64,000
- System.Threading.OverlappedData64,000
- System.SByte[]2,797
- System.String2,153
- System.Object[]952
- System.Reflection.RuntimeParameterInfo650
- System.Byte[]539
- System.Diagnostics.Tracing.ScalarTypeInfo489
- System.Func<,,>485
- System.Reflection.ParameterInfo[]281
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>251
- System.IO.AsyncWindowsFileStreamStrategy250
Allocations after this PR
TypeAllocations
-System.IO.Strategies.AsyncWindowsFileStreamStrategy.MemoryFileStreamValueTaskSource64,000
- System.Threading.ThreadPoolBoundHandleOverlapped64,000
- System.Threading.OverlappedData64,000
- System.SByte[]2,803
- System.String2,144
- System.Object[]952
- System.Reflection.RuntimeParameterInfo650
- System.Byte[]539
- System.Reflection.ParameterInfo[]281
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>251
- System.IO.Strategies.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandle250
- System.Reflection.RuntimeMethodInfo241
- System.Signature225

ReadAsync(bufferSize: 1, userBufferSize: HalfKibibyte, FileOptions.Asynchronous)

Allocations before this PR
TypeAllocations
-System.Threading.Tasks.Task<>512,001
-System.IO.MemoryFileStreamCompletionSource512,000
- System.Threading.ThreadPoolBoundHandleOverlapped512,000
- System.Threading.OverlappedData512,000
- System.SByte[]2,806
- System.String2,153
- System.Object[]957
- System.Reflection.RuntimeParameterInfo650
- System.Byte[]539
- System.Diagnostics.Tracing.ScalarTypeInfo489
- System.Func<,,>485
- System.Reflection.ParameterInfo[]281
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>251
- System.IO.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandle250
Allocations after this PR
TypeAllocations
-System.IO.Strategies.AsyncWindowsFileStreamStrategy.MemoryFileStreamValueTaskSource512,000
- System.Threading.ThreadPoolBoundHandleOverlapped512,000
- System.Threading.OverlappedData512,000
- System.SByte[]2,803
- System.String2,144
- System.Object[]964
- System.Reflection.RuntimeParameterInfo650
- System.Byte[]539
- System.Reflection.ParameterInfo[]281
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>251
- System.IO.Strategies.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandle250
- System.Reflection.RuntimeMethodInfo241
- System.Signature225
- System.RuntimeType[]178

ReadAsync(bufferSize: FourKibibytes, userBufferSize: FourKibibytes, FileOptions.Asynchronous)

Allocations before this PR
TypeAllocations
-System.Threading.Tasks.Task<>64,013
-System.IO.MemoryFileStreamCompletionSource64,000
- System.Threading.ThreadPoolBoundHandleOverlapped64,000
- System.Threading.OverlappedData64,000
- System.SByte[]2,840
- System.String2,153
- System.Object[]955
- System.Reflection.RuntimeParameterInfo650
- System.Byte[]539
- System.Diagnostics.Tracing.ScalarTypeInfo489
- System.Func<,,>485
- System.Reflection.ParameterInfo[]281
- System.IO.BufferedFileStreamStrategy252
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>251
- System.IO.AsyncWindowsFileStreamStrategy250
- System.Threading.SemaphoreSlim250
- System.Threading.ThreadPoolBoundHandle250
Allocations after this PR
TypeAllocations
-System.IO.Strategies.AsyncWindowsFileStreamStrategy.MemoryFileStreamValueTaskSource64,000
- System.Threading.ThreadPoolBoundHandleOverlapped64,000
- System.Threading.OverlappedData64,000
- System.SByte[]2,836
- System.String2,144
- System.Object[]957
- System.Reflection.RuntimeParameterInfo650
- System.Byte[]539
- System.Reflection.ParameterInfo[]281
- System.IO.Strategies.BufferedFileStreamStrategy252
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>251
- System.IO.Strategies.AsyncWindowsFileStreamStrategy250
- System.Threading.SemaphoreSlim250
- System.Threading.ThreadPoolBoundHandle250
- System.Runtime.CompilerServices.StrongBox<>250
- System.Reflection.RuntimeMethodInfo241
- System.Signature225

ReadAsync(bufferSize: FourKibibytes, userBufferSize: HalfKibibyte, FileOptions.Asynchronous)

Allocations before this PR
TypeAllocations
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>64,235
-System.Threading.Tasks.Task<>64,015
-System.IO.FileStreamCompletionSource64,000
- System.SByte[]2,916
- System.String2,155
- System.Object[]956
- System.Byte[]789
- System.Reflection.RuntimeParameterInfo650
- System.Diagnostics.Tracing.ScalarTypeInfo489
- System.Func<,,>485
- System.Reflection.ParameterInfo[]281
- System.IO.BufferedFileStreamStrategy252
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.IO.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandleOverlapped250
- System.Threading.OverlappedData250
- System.Threading.SemaphoreSlim250
- System.Threading.ThreadPoolBoundHandle250
- System.Threading.PreAllocatedOverlapped250
Allocations after this PR
TypeAllocations
-System.IO.Strategies.AsyncWindowsFileStreamStrategy.FileStreamValueTaskSource64,000
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>63,812
- System.SByte[]2,909
- System.String2,144
- System.Object[]964
- System.Byte[]789
- System.Reflection.RuntimeParameterInfo650
- System.Reflection.ParameterInfo[]281
- System.IO.Strategies.BufferedFileStreamStrategy252
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.IO.Strategies.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandleOverlapped250
- System.Threading.OverlappedData250
- System.Threading.SemaphoreSlim250
- System.Threading.ThreadPoolBoundHandle250
- System.Threading.PreAllocatedOverlapped250
- System.Runtime.CompilerServices.StrongBox<>250
- System.Reflection.RuntimeMethodInfo241
- System.Signature225

WriteAsync(bufferSize: 1, userBufferSize: FourKibibytes, FileOptions.Asynchronous)

Allocations before this PR
TypeAllocations
-System.Threading.Tasks.Task<>64,001
-System.IO.MemoryFileStreamCompletionSource64,000
- System.Threading.ThreadPoolBoundHandleOverlapped64,000
- System.Threading.OverlappedData64,000
- System.SByte[]2,798
- System.String2,153
- System.Object[]954
- System.Reflection.RuntimeParameterInfo650
- System.Byte[]539
- System.Diagnostics.Tracing.ScalarTypeInfo489
- System.Func<,,>485
- System.Reflection.ParameterInfo[]281
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>251
- System.IO.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandle250
Allocations after this PR
TypeAllocations
-System.IO.Strategies.AsyncWindowsFileStreamStrategy.MemoryFileStreamValueTaskSource64,000
- System.Threading.ThreadPoolBoundHandleOverlapped64,000
- System.Threading.OverlappedData64,000
- System.SByte[]2,790
- System.String2,144
- System.Object[]959
- System.Reflection.RuntimeParameterInfo650
- System.Byte[]539
- System.Reflection.ParameterInfo[]281
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>251
- System.IO.Strategies.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandle250
- System.Reflection.RuntimeMethodInfo241
- System.Signature225
- System.RuntimeType[]178

WriteAsync(bufferSize: 1, userBufferSize: HalfKibibyte, FileOptions.Asynchronous)

Allocations before this PR
TypeAllocations
-System.Threading.Tasks.Task<>512,001
-System.IO.MemoryFileStreamCompletionSource512,000
- System.Threading.ThreadPoolBoundHandleOverlapped512,000
- System.Threading.OverlappedData512,000
- System.SByte[]2,812
- System.String2,153
- System.Object[]959
- System.Reflection.RuntimeParameterInfo650
- System.Byte[]539
- System.Diagnostics.Tracing.ScalarTypeInfo489
- System.Func<,,>485
- System.Reflection.ParameterInfo[]281
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>251
- System.IO.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandle250
Allocations after this PR
TypeAllocations
-System.IO.Strategies.AsyncWindowsFileStreamStrategy.MemoryFileStreamValueTaskSource512,000
- System.Threading.ThreadPoolBoundHandleOverlapped512,000
- System.Threading.OverlappedData512,000
- System.SByte[]2,790
- System.String2,144
- System.Object[]958
- System.Reflection.RuntimeParameterInfo650
- System.Byte[]539
- System.Reflection.ParameterInfo[]281
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>251
- System.IO.Strategies.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandle250
- System.Reflection.RuntimeMethodInfo241
- System.Signature225
- System.RuntimeType[]178

WriteAsync(bufferSize: FourKibibytes, userBufferSize: FourKibibytes, FileOptions.Asynchronous)

Allocations before this PR
TypeAllocations
-System.Threading.Tasks.Task<>64,014
- System.Threading.ThreadPoolBoundHandleOverlapped32,250
- System.Threading.OverlappedData32,250
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>32,249
-System.IO.MemoryFileStreamCompletionSource32,000
-System.IO.FileStreamCompletionSource32,000
- System.SByte[]2,915
- System.String2,155
- System.Object[]952
- System.Byte[]789
- System.Reflection.RuntimeParameterInfo650
- System.Diagnostics.Tracing.ScalarTypeInfo489
- System.Func<,,>485
- System.Reflection.ParameterInfo[]281
- System.IO.BufferedFileStreamStrategy252
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.IO.AsyncWindowsFileStreamStrategy250
- System.Threading.SemaphoreSlim250
- System.Threading.ThreadPoolBoundHandle250
- System.Threading.PreAllocatedOverlapped250
Allocations after this PR
TypeAllocations
- System.Threading.ThreadPoolBoundHandleOverlapped32,250
- System.Threading.OverlappedData32,250
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>32,246
-System.IO.Strategies.AsyncWindowsFileStreamStrategy.MemoryFileStreamValueTaskSource32,000
-System.IO.Strategies.AsyncWindowsFileStreamStrategy.FileStreamValueTaskSource32,000
- System.SByte[]2,893
- System.String2,144
- System.Object[]954
- System.Byte[]789
- System.Reflection.RuntimeParameterInfo650
- System.Reflection.ParameterInfo[]281
- System.IO.Strategies.BufferedFileStreamStrategy252
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.IO.Strategies.AsyncWindowsFileStreamStrategy250
- System.Threading.SemaphoreSlim250
- System.Threading.ThreadPoolBoundHandle250
- System.Threading.PreAllocatedOverlapped250
- System.Runtime.CompilerServices.StrongBox<>250
- System.Reflection.RuntimeMethodInfo241
- System.Signature225

WriteAsync(bufferSize: FourKibibytes, userBufferSize: HalfKibibyte, FileOptions.Asynchronous)

Allocations before this PR
TypeAllocations
-System.Threading.Tasks.Task<>64,014
-System.IO.FileStreamCompletionSource64,000
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>63,963
- System.SByte[]2,915
- System.String2,155
- System.Object[]956
- System.Byte[]789
- System.Reflection.RuntimeParameterInfo650
- System.Diagnostics.Tracing.ScalarTypeInfo489
- System.Func<,,>485
- System.Reflection.ParameterInfo[]281
- System.IO.BufferedFileStreamStrategy252
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.IO.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandleOverlapped250
- System.Threading.OverlappedData250
- System.Threading.SemaphoreSlim250
- System.Threading.ThreadPoolBoundHandle250
- System.Threading.PreAllocatedOverlapped250
Allocations after this PR
TypeAllocations
-System.IO.Strategies.AsyncWindowsFileStreamStrategy.FileStreamValueTaskSource64,000
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<>.AsyncStateMachineBox<>63,830
- System.SByte[]2,912
- System.String2,144
- System.Object[]966
- System.Byte[]789
- System.Reflection.RuntimeParameterInfo650
- System.Reflection.ParameterInfo[]281
- System.IO.Strategies.BufferedFileStreamStrategy252
- Microsoft.Win32.SafeHandles.SafeFileHandle252
- System.IO.FileStream252
- System.IO.Strategies.AsyncWindowsFileStreamStrategy250
- System.Threading.ThreadPoolBoundHandleOverlapped250
- System.Threading.OverlappedData250
- System.Threading.SemaphoreSlim250
- System.Threading.ThreadPoolBoundHandle250
- System.Threading.PreAllocatedOverlapped250
- System.Runtime.CompilerServices.StrongBox<>250
- System.Threading.Tasks.ValueTask.ValueTaskSourceAsTask249
- System.Threading.QueueUserWorkItemCallbackDefaultContext<>249

@adamsitnik@jozkee@jeffhandley@stephentoub

Also: Don't forget to ignore whitespace changes so it's easier to review the nested type's changes 😃
Author:carlossanlop
Assignees:carlossanlop
Labels:

area-System.IO

Milestone:6.0.0

@carlossanlop
Copy link
ContributorAuthor

Tagging subscribers to this area:@carlossanlop

Yes, thank you bot... 😆

adamsitnik reacted with laugh emoji

Copy link
Member

@jozkeejozkee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Otherwise; LGTM, will approve if/when comments are resolved, most of them are nits and/or questions.

Comment on lines 17 to 22
internalconstlongNoResult=0;
internalconstlongResultSuccess=(long)1<<32;
internalconstlongResultError=(long)2<<32;
internalconstlongRegisteringCancellation=(long)4<<32;
internalconstlongCompletedCallback=(long)8<<32;
internalconstulongResultMask=((ulong)uint.MaxValue)<<32;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Consider prefixing this constants withAsyncCompletionSource_ or something alike since these names are too ambiguous or keep them in their respective classes regardless of them being duplicated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

or put them into a nested static class calledReturnCodes:

internalstaticpartialclassFileStreamHelpers{internalstaticclassReturnCodes{internalconstlongNoResult=0;internalconstlongResultSuccess=(long)1<<32;internalconstlongResultError=(long)2<<32;internalconstlongRegisteringCancellation=(long)4<<32;internalconstlongCompletedCallback=(long)8<<32;internalconstulongResultMask=((ulong)uint.MaxValue)<<32;}

but it's a nit (not a blocker)

Comment on lines 66 to 67
_strategy.CompareExchangeCurrentOverlappedOwner(this,null)==null?
_strategy._fileHandle.ThreadPoolBinding!.AllocateNativeOverlapped(preallocatedOverlapped!):// allocated when buffer was created, and buffer is non-null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Suggested change
_strategy.CompareExchangeCurrentOverlappedOwner(this,null)==null?
_strategy._fileHandle.ThreadPoolBinding!.AllocateNativeOverlapped(preallocatedOverlapped!):// allocated when buffer was created, and buffer is non-null
strategy.CompareExchangeCurrentOverlappedOwner(this,null)==null?
strategy._fileHandle.ThreadPoolBinding!.AllocateNativeOverlapped(preallocatedOverlapped!):// allocated when buffer was created, and buffer is non-null

// be directly the AwaitableProvider that's completing (in the case where the preallocated
// overlapped was already in use by another operation).
object?state=ThreadPoolBoundHandle.GetNativeOverlappedState(pOverlapped);
Debug.Assert(stateis(AsyncWindowsFileStreamStrategy orFileStreamValueTaskSource));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Suggested change
Debug.Assert(stateis(AsyncWindowsFileStreamStrategy orFileStreamValueTaskSource));
Debug.Assert(stateisAsyncWindowsFileStreamStrategy orFileStreamValueTaskSource);

interrorCode=unchecked((int)(packedResult&uint.MaxValue));
if(errorCode==Interop.Errors.ERROR_OPERATION_ABORTED)
{
_source.SetException(ExceptionDispatchInfo.SetCurrentStackTrace(newOperationCanceledException(cancellationToken.IsCancellationRequested?cancellationToken:newCancellationToken(canceled:true))));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Suggested change
_source.SetException(ExceptionDispatchInfo.SetCurrentStackTrace(newOperationCanceledException(cancellationToken.IsCancellationRequested?cancellationToken:newCancellationToken(canceled:true))));
Exceptione=newOperationCanceledException(cancellationToken.IsCancellationRequested?cancellationToken:newCancellationToken(canceled:true));
e.SetCurrentStackTrace();
_source.SetException(e);

}

privatevoidCancel(CancellationTokentoken)
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Missing comment and asserts:

// WARNING: This may potentially be called under a lock (during cancellation registration)
Debug.Assert(stateisFileStreamCompletionSource,"Unknown state passed to cancellation");
FileStreamCompletionSourcecompletionSource=(FileStreamCompletionSource)state;
Debug.Assert(completionSource._overlapped!=null&&!completionSource.Task.IsCompleted,"IO should not have completed yet");

adamsitnik reacted with thumbs up emoji
Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The first assert is not needed because the method argument is not anobject? state.

I'll add the assert that checks for overlapped an completed status.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Thestate param could be added to this method and allows you to conserve the first Debug.Assert.

longpackedResult=Interlocked.CompareExchange(ref_result,FileStreamHelpers.RegisteringCancellation,FileStreamHelpers.NoResult);
if(packedResult==FileStreamHelpers.NoResult)
{
_cancellationRegistration=cancellationToken.UnsafeRegister(static(s,token)=>((FileStreamValueTaskSource)s!).Cancel(token),this);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Could we pass the callback on a similar fashion to FileStreamCompletionSource

_cancellationRegistration=cancellationToken.UnsafeRegister(cancelCallback,this);

Or why did you opted for this way instead?

Copy link
ContributorAuthor

@carlossanlopcarlossanlopApr 8, 2021
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It made more sense to passthis as the second argument ofUnsafeRegister, which ensures it's aValueTaskSource instance, and saves us from the check of theobject? state parameter like in the old code. The old codealso allocates anAction<object?> instance to save a reference to theCancel method.

We have asimilar example in System.Threading.Channels.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It made more sense to pass this as the second argument of UnsafeRegister

FileStreamCompletionSource was also passingthis as 2nd argument.

The old code also allocates an Action<object?> instance to save a reference to the Cancel method.

Yes, seems that it was caching the method group to avoid allocating everytime it was passed toUnsafeRegister.
However I think you can avoid the allocation without caching yourself by doing this:

_cancellationRegistration=cancellationToken.UnsafeRegister((s,token)=>Cancel(s,token),this);

Just a suggestion, not something that you should block on.

@davidfowl
Copy link
Member

This is the change I was waiting for 👏🏾👏🏾

@davidfowl
Copy link
Member

I haven't looked but I assume we'll reuse the operation objects if there's no concurrency? This is what sockets do today so in the 95% case of no overlapping writes or reads, there'll be a single reusable allocation on the FileStream

@stephentoub
Copy link
Member

This is the change I was waiting for

It's not, actually. You want the next one where the IValueTaskSource instance is cached.

}
// else: Some other thread stole the result, so now it is responsible to finish the callback
}
// else: Some other thread is registering a cancellation, so it *must* finish the callback
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

All this logic looks more complicated than it needs to be. I realize you're just following the same flow as what was already there, so nothing needs to be changed in this PR. But we should revisit all of this synchronization later; I suspect we can do it more succinctly and efficiently.

carlossanlop reacted with thumbs up emoji
Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I opened an issue to follow up on this and on the caching of the IValueTaskSource.
#50972

adamsitnik reacted with thumbs up emoji
Copy link
Member

@adamsitnikadamsitnik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Overall looks good to me, but before we merge I would like to see the following benchmark results:

.NET 6 with new strategy enabled: before and after your change. (not .NET 5 vs 6 as there are too many moving targets)

You should be able to achieve that by running the following command in the perf repo;

git clone https://github.com/dotnet/performance.gitcd performance\src\benchmarks\microdotnet run -c Release -f net6.0 --filter *Perf_FileStream* --coreRun $pathToCoreRunWithoutYourChanges $pathToCoreRunWithYourChanges --envVars"DOTNET_SYSTEM_IO_USENET5COMPATFILESTREAM:0"

/// <see cref="MemoryHandle"/> when the operation has completed. This should only be used
/// when memory doesn't wrap a byte[].
/// </summary>
privatesealedclassMemoryAwaitableProvider:FileStreamValueTaskSource
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This should only be used when memory doesn't wrap a byte[].

I just realized that we don't have benchmarks for this scenario asMemory used in our micro-benchmarks always wraps a byte array. Could you please add such benchmarks tohttps://github.com/dotnet/performance/blob/main/src/benchmarks/micro/libraries/System.IO.FileSystem/Perf.FileStream.cs ? One test case should be enough as this should be rare.

Copy link
ContributorAuthor

@carlossanlopcarlossanlopApr 7, 2021
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Some of the benchmarks do consume this type. If you look at some of the profiling tests I executed, you'll see thatMemoryValueTaskSource shows up a few times. The profiling tests reuse the code we had in our benchmarks.

I don't mind adding an explicit one though. Dou you want me to add one anyway?

Comment on lines 17 to 22
internalconstlongNoResult=0;
internalconstlongResultSuccess=(long)1<<32;
internalconstlongResultError=(long)2<<32;
internalconstlongRegisteringCancellation=(long)4<<32;
internalconstlongCompletedCallback=(long)8<<32;
internalconstulongResultMask=((ulong)uint.MaxValue)<<32;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

or put them into a nested static class calledReturnCodes:

internalstaticpartialclassFileStreamHelpers{internalstaticclassReturnCodes{internalconstlongNoResult=0;internalconstlongResultSuccess=(long)1<<32;internalconstlongResultError=(long)2<<32;internalconstlongRegisteringCancellation=(long)4<<32;internalconstlongCompletedCallback=(long)8<<32;internalconstulongResultMask=((ulong)uint.MaxValue)<<32;}

but it's a nit (not a blocker)

Comment on lines 62 to 63
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Did you run the perf. benchmarks after doing this change and did you notice any impact?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Pending.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Confirmed - There is a slight improvement.

I made sure to compare apples to apples: I used my latest commit, and built it settingRunContinuationsAsynchronously totrue, then tofalse. I compared it both times against our code in .NET 5.0.

The improvement is clear when setting it tofalse (or not setting it at all).

RunContinuationsAsynchronously = true

❯ dotnet run -c release --base "D:\fs50" --diff "D:\fs60_NEW_ON_IVTS_asynctrue" --threshold 0.0001%
summary:
better: 24, geomean: 3.402
worse: 19, geomean: 2.244
total diff: 43

Slowerdiff/baseBase Median (ns)Diff Median (ns)Modality
Perf_FileStream.CopyToFile(fileSize: 1048576, options: None)5.49882252.574840275.00
Perf_FileStream.CopyToFileAsync(fileSize: 1048576, options: None)5.121003671.885136350.00bimodal
Perf_FileStream.WriteByte(fileSize: 1024, options: None)4.44257218.231141827.01
Perf_FileStream.WriteByte(fileSize: 1024, options: Asynchronous)4.08286859.381171600.96
Perf_FileStream.Write(fileSize: 1048576, userBufferSize: 4096, options: None)3.251795459.385832233.33
Perf_FileStream.Write(fileSize: 1048576, userBufferSize: 512, options: None)3.111844293.365741847.92several?
Perf_FileStream.CopyToFileAsync(fileSize: 1048576, options: Asynchronous)3.111868294.535807825.00
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 4096, options: None)3.082209384.386796443.75
Perf_FileStream.CopyToFileAsync(fileSize: 1024, options: Asynchronous)2.46557062.051371566.15
Perf_FileStream.CopyToFile(fileSize: 1024, options: None)2.33478821.501116033.48
Perf_FileStream.CopyToFileAsync(fileSize: 1024, options: None)2.28543512.611239956.25bimodal
Perf_FileStream.FlushAsync(fileSize: 1024, options: None)1.613479778.755612725.00
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 512, options: Asynchronous)1.448197915.6311814575.00
Perf_FileStream.WriteAsync(fileSize: 104857600, userBufferSize: 4096, options: None)1.33168291900.00224355000.00
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 512, options: None)1.335313143.757063218.75
Perf_FileStream.Flush(fileSize: 1024, options: None)1.253448111.254313832.81
Perf_FileStream.CopyToFile(fileSize: 104857600, options: None)1.1250055150.0056309025.00
Perf_FileStream.CopyToFileAsync(fileSize: 104857600, options: None)1.1256033900.0062803900.00
Perf_FileStream.Write(fileSize: 104857600, userBufferSize: 4096, options: None)1.12133329050.00148833000.00bimodal
Fasterbase/diffBase Median (ns)Diff Median (ns)Modality
Perf_FileStream.SeekBackward(fileSize: 1024, options: Asynchronous)117.075489541.6746889.31
Perf_FileStream.SeekForward(fileSize: 1024, options: Asynchronous)65.932811281.2542642.74
Perf_FileStream.SeekBackward(fileSize: 1024, options: None)57.842571736.4644462.45
Perf_FileStream.SeekForward(fileSize: 1024, options: None)21.09876923.2641586.04
Perf_FileStream.OpenClose(fileSize: 1024, options: None)5.00152781.6630548.62
Perf_FileStream.OpenClose(fileSize: 1024, options: Asynchronous)4.76153731.8132314.04
Perf_FileStream.WriteAsync(fileSize: 104857600, userBufferSize: 4096, options: Asynchronous)4.032463834750.00611183400.00
Perf_FileStream.ReadByte(fileSize: 1024, options: None)3.93165696.6842208.34
Perf_FileStream.ReadByte(fileSize: 1024, options: Asynchronous)3.52200423.3656865.52
Perf_FileStream.LockUnlock(fileSize: 1024, options: None)2.62230670.3887990.73
Perf_FileStream.LockUnlock(fileSize: 1024, options: Asynchronous)2.58238920.2292478.58
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 512, options: None)2.343222202.501377939.84
Perf_FileStream.CopyToFileAsync(fileSize: 104857600, options: Asynchronous)1.87156593600.0083681962.50
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 4096, options: Asynchronous)1.6619598723.0811803641.67
Perf_FileStream.Flush(fileSize: 1024, options: Asynchronous)1.6322492768.7513818900.00
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 4096, options: Asynchronous)1.577025697.924487121.88
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 4096, options: Asynchronous)1.47686776300.00466936500.00bimodal
Perf_FileStream.Read(fileSize: 1048576, userBufferSize: 4096, options: None)1.33987460.55743825.45
Perf_FileStream.Read(fileSize: 1048576, userBufferSize: 512, options: None)1.24975146.09784156.88
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 512, options: Asynchronous)1.195221529.174387085.94
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 4096, options: None)1.181301925.001103803.79
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 4096, options: None)1.04116486775.00112407200.00
Perf_FileStream.FlushAsync(fileSize: 1024, options: Asynchronous)1.0321953590.9121241122.73
Perf_FileStream.Read(fileSize: 104857600, userBufferSize: 4096, options: None)1.0188348175.0087467100.00
RunContinuationsAsynchronously = false

dotnet run -c release --base "D:\fs50" --diff "D:\fs60_NEW_ON_IVTS_asyncfalse" --threshold 0.0001%
summary:
better: 23, geomean: 3.592
worse: 20, geomean: 2.135
total diff: 43

Slowerdiff/baseBase Median (ns)Diff Median (ns)Modality
Perf_FileStream.CopyToFile(fileSize: 1048576, options: None)5.36882252.574730875.00
Perf_FileStream.CopyToFileAsync(fileSize: 1048576, options: None)5.051003671.885071912.50bimodal
Perf_FileStream.WriteByte(fileSize: 1024, options: None)4.25257218.231094164.38several?
Perf_FileStream.WriteByte(fileSize: 1024, options: Asynchronous)4.05286859.381161160.27
Perf_FileStream.Write(fileSize: 1048576, userBufferSize: 4096, options: None)3.231795459.385791481.25
Perf_FileStream.Write(fileSize: 1048576, userBufferSize: 512, options: None)3.101844293.365709820.83several?
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 4096, options: None)3.092209384.386836504.17
Perf_FileStream.CopyToFileAsync(fileSize: 1048576, options: Asynchronous)3.071868294.535733883.33
Perf_FileStream.CopyToFileAsync(fileSize: 1024, options: Asynchronous)2.46557062.051368034.90
Perf_FileStream.CopyToFile(fileSize: 1024, options: None)2.31478821.501103696.25
Perf_FileStream.CopyToFileAsync(fileSize: 1024, options: None)2.24543512.611218234.38bimodal
Perf_FileStream.FlushAsync(fileSize: 1024, options: None)1.603479778.755572682.29
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 512, options: Asynchronous)1.428197915.6311634850.00
Perf_FileStream.WriteAsync(fileSize: 104857600, userBufferSize: 4096, options: None)1.33168291900.00223141800.00bimodal
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 512, options: None)1.315313143.756949831.25
Perf_FileStream.Flush(fileSize: 1024, options: None)1.263448111.254332915.63
Perf_FileStream.Write(fileSize: 104857600, userBufferSize: 4096, options: None)1.13133329050.00150223800.00bimodal
Perf_FileStream.CopyToFile(fileSize: 104857600, options: None)1.1150055150.0055699050.00
Perf_FileStream.CopyToFileAsync(fileSize: 104857600, options: None)1.1056033900.0061710550.00
Perf_FileStream.Read(fileSize: 104857600, userBufferSize: 4096, options: None)1.0088348175.0088759625.00
Fasterbase/diffBase Median (ns)Diff Median (ns)Modality
Perf_FileStream.SeekBackward(fileSize: 1024, options: Asynchronous)116.975489541.6746929.42
Perf_FileStream.SeekForward(fileSize: 1024, options: Asynchronous)65.762811281.2542753.29
Perf_FileStream.SeekBackward(fileSize: 1024, options: None)57.562571736.4644676.15
Perf_FileStream.SeekForward(fileSize: 1024, options: None)21.52876923.2640756.47
Perf_FileStream.OpenClose(fileSize: 1024, options: None)5.06152781.6630216.63
Perf_FileStream.OpenClose(fileSize: 1024, options: Asynchronous)4.81153731.8131975.57
Perf_FileStream.WriteAsync(fileSize: 104857600, userBufferSize: 4096, options: Asynchronous)4.052463834750.00608864800.00
Perf_FileStream.ReadByte(fileSize: 1024, options: None)3.99165696.6841526.73
Perf_FileStream.ReadByte(fileSize: 1024, options: Asynchronous)3.54200423.3656610.08
Perf_FileStream.LockUnlock(fileSize: 1024, options: None)2.62230670.3887877.10
Perf_FileStream.LockUnlock(fileSize: 1024, options: Asynchronous)2.59238920.2292291.75
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 512, options: None)2.403222202.501345116.25
Perf_FileStream.CopyToFileAsync(fileSize: 104857600, options: Asynchronous)1.86156593600.0083969875.00
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 4096, options: Asynchronous)1.6419598723.0811925173.68
Perf_FileStream.Flush(fileSize: 1024, options: Asynchronous)1.6222492768.7513859764.71
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 4096, options: Asynchronous)1.567025697.924493881.25
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 4096, options: Asynchronous)1.46686776300.00470158300.00bimodal
Perf_FileStream.Read(fileSize: 1048576, userBufferSize: 4096, options: None)1.32987460.55750294.64
Perf_FileStream.Read(fileSize: 1048576, userBufferSize: 512, options: None)1.24975146.09784056.56
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 4096, options: None)1.171301925.001108880.36
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 512, options: Asynchronous)1.175221529.174470673.44
Perf_FileStream.FlushAsync(fileSize: 1024, options: Asynchronous)1.0721953590.9120580725.00
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 4096, options: None)1.01116486775.00

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Here is the result comparison if we compare the benchmark results of setting it totrue vsfalse:

base: RunContinuationsAsynchronously=true, diff: RunContinuationsAsynchronously=false

dotnet run -c release --base "D:\fs60_NEW_ON_IVTS_asynctrue" --diff "D:\fs60_NEW_ON_IVTS_asyncfalse" --threshold 0.0001%
summary:
better: 14, geomean: 1.018
worse: 11, geomean: 1.017
total diff: 25

Slowerdiff/baseBase Median (ns)Diff Median (ns)Modality
Perf_FileStream.WriteAsync_NoBuffering(fileSize: 104857600, userBufferSize: 16384, options: None)1.0567143550.0070259187.50
Perf_FileStream.ReadAsync(fileSize: 104857600, userBufferSize: 4096, options: None)1.02112407200.00115107550.00
Perf_FileStream.Read_NoBuffering(fileSize: 1048576, userBufferSize: 16384, options: None)1.02240016.67244921.93
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 512, options: Asynchronous)1.024387085.944470673.44
Perf_FileStream.Read(fileSize: 104857600, userBufferSize: 4096, options: None)1.0187467100.0088759625.00
Perf_FileStream.WriteAsync_NoBuffering(fileSize: 1048576, userBufferSize: 16384, options: None)1.0121684354.5521982809.09
Perf_FileStream.ReadAsync_NoBuffering(fileSize: 1048576, userBufferSize: 16384, options: Asynchronous)1.011236709.901253242.19
Perf_FileStream.Read(fileSize: 1024, userBufferSize: 1024, options: None)1.0139289.5439730.73
Perf_FileStream.Write_NoBuffering(fileSize: 1048576, userBufferSize: 16384, options: None)1.0121445427.2721646650.00
Perf_FileStream.Read(fileSize: 1048576, userBufferSize: 4096, options: None)1.01743825.45750294.64
Perf_FileStream.SeekBackward(fileSize: 1024, options: None)1.0044462.4544676.15
Fasterbase/diffBase Median (ns)Diff Median (ns)Modality
Perf_FileStream.WriteByte(fileSize: 1024, options: None)1.041141827.011094164.38several?
Perf_FileStream.ReadAsync_NoBuffering(fileSize: 104857600, userBufferSize: 16384, options: None)1.0345688612.5044474575.00
Perf_FileStream.ReadAsync(fileSize: 1048576, userBufferSize: 512, options: None)1.021377939.841345116.25
Perf_FileStream.CopyToFile(fileSize: 1048576, options: None)1.024840275.004730875.00
Perf_FileStream.SeekForward(fileSize: 1024, options: None)1.0241586.0440756.47
Perf_FileStream.ReadAsync_NoBuffering(fileSize: 104857600, userBufferSize: 16384, options: Asynchronous)1.02130849050.00128463775.00
Perf_FileStream.ReadByte(fileSize: 1024, options: None)1.0242208.3441526.73
Perf_FileStream.WriteAsync(fileSize: 1048576, userBufferSize: 512, options: Asynchronous)1.0211814575.0011634850.00
Perf_FileStream.CopyToFileAsync(fileSize: 1048576, options: Asynchronous)1.015807825.005733883.33
Perf_FileStream.CopyToFileAsync(fileSize: 1048576, options: None)1.015136350.005071912.50
Perf_FileStream.CopyToFile(fileSize: 104857600, options: None)1.0156309025.0055699050.00
Perf_FileStream.Read_NoBuffering(fileSize: 104857600, userBufferSize: 16384, options: None)1.0133747814.293338345.14
Perf_FileStream.OpenClose(fileSize: 1024, options: Asynchronous)1.0132314.0431975.57
Perf_FileStream.ReadAsync(fileSize: 1024, userBufferSize: 1024, options: Asynchronous)1.0081988.0981641.78

jozkee reacted with thumbs up emoji
longpackedResult=Interlocked.CompareExchange(ref_result,FileStreamHelpers.RegisteringCancellation,FileStreamHelpers.NoResult);
if(packedResult==FileStreamHelpers.NoResult)
{
_cancellationRegistration=cancellationToken.UnsafeRegister(static(s,token)=>((FileStreamValueTaskSource)s!).Cancel(token),this);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

It made more sense to pass this as the second argument of UnsafeRegister

FileStreamCompletionSource was also passingthis as 2nd argument.

The old code also allocates an Action<object?> instance to save a reference to the Cancel method.

Yes, seems that it was caching the method group to avoid allocating everytime it was passed toUnsafeRegister.
However I think you can avoid the allocation without caching yourself by doing this:

_cancellationRegistration=cancellationToken.UnsafeRegister((s,token)=>Cancel(s,token),this);

Just a suggestion, not something that you should block on.

Comment on lines 120 to 122
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I tried, but aValueTask does not have aResult property, like aValueTask<T> does, because it returns void. So I'm not sure if it's possible to apply an equivalent pattern.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

publicoverridevoidWrite(byte[]buffer,intoffset,intcount){ValueTaskvt=WriteAsyncInternal(newReadOnlyMemory<byte>(buffer,offset,count),CancellationToken.None);if(vt.IsCompleted){vt.GetAwaiter().GetResult();}else{vt.AsTask().GetAwaiter().GetResult();}}

That said, it's not much of an issue for this case. The read side is an issue because AsTask() will almost certainly need to allocate a newTask<int> in order to hand back the result, but for the write side, AsTask() on an already completed ValueTask will just return Task.Completed, assuming the operation completed successfully. So the only time the existing code for the write side will allocate is when the operation was canceled or faulted, in which case we're about to throw an exception anyway, and the extra task allocation won't really matter. Up to you whether you want to change it.

carlossanlop reacted with thumbs up emoji
Copy link
Member

@jozkeejozkee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

LGTM; thanks Carlos!

carlossanlop reacted with rocket emoji
@adamsitnik
Copy link
Member

I compared it both times against our code in .NET 5.0.

Comparison against 5.0 is a comparison of all of the changes that were merged to main since we have snapped 5.0 with your change against 5.0. What we care about is how your change (and nothing else) is affecting the perf, so we need to isolate the runs to only your change.

To be able to do that you need to run the benchmarks using two CoreRuns:

  • as a baseline the corerun that was built from main when you have created a new branch, without any of your changes
  • your branch with your changes

My personal workflow:

  1. When I create a new branch, I perform a full build inRelease:
build -c Release -subset clr+libs
  1. I copy the folder that containsCoreRun.exe (runtime\artifacts\bin\testhost\net6.0-windows-Release-x64\shared\Microsoft.NETCore.App\6.0.0) to a separate folder. Typically I call it "before".
  2. I apply some changes and perform an incremental Release build. In case ofSystem.Private.CoreLib.dll it's:
build -c Release -subset clr.corelib+clr.nativecorelib+libs.PreTest
  1. I run the benchmarks using the old and newCoreRun.exe:
dotnet run -c Release -f net6.0 --filter *ABC* --corerun C:\Projects\runtime\artifacts\bin\testhost\net6.0-windows-Release-x64\shared\Microsoft.NETCore.App\before\CoreRun.exe C:\Projects\runtime\artifacts\bin\testhost\net6.0-windows-Release-x64\shared\Microsoft.NETCore.App\6.0.0\corerun.exe

@carlossanlop
Copy link
ContributorAuthor

carlossanlop commentedApr 13, 2021
edited
Loading

  • as a baseline the corerun that was built from main when you have created a new branch, without any of your changes
  • your branch with your changes

@adamsitnik@jozkee here's the table comparing the before (original) and after of this PR. Most results had allocation improvements, but worse speed when FileOptions = Asynchronous:

Results

Command:

 dotnet run    -c Release    -f net6.0    --corerun        D:\runtime2\artifacts\bin\testhost\net6.0-windows-Release-x64\shared\Microsoft.NETCore.App\6.0.0\corerun.exe        D:\runtime\artifacts\bin\testhost\net6.0-windows-Release-x64\shared\Microsoft.NETCore.App\6.0.0\corerun.exe    --filter System.IO.Tests.Perf_FileStream.*Async*    --statisticalTest 3ms    --minIterationCount 100    --maxIterationCount 101    --artifacts "D:\finalresults"

Note: The runtime2 corerun contains the baseline commit before my changes, the runtime corerun contains this PR's changes.

MethodToolchainfileSizeuserBufferSizeoptionsMeanErrorStdDevMedianMinMaxRatioAllocated
ReadAsyncBEFORE10241024None78.69 μs0.255 μs0.736 μs78.53 μs77.61 μs80.66 μs1.005 KB
ReadAsyncAFTER10241024None79.10 μs0.237 μs0.673 μs79.08 μs76.96 μs80.71 μs1.015 KB
WriteAsyncBEFORE10241024None316.97 μs2.512 μs6.919 μs315.92 μs305.11 μs339.35 μs1.004 KB
WriteAsyncAFTER10241024None300.13 μs5.643 μs15.730 μs297.59 μs272.46 μs348.15 μs0.954 KB
ReadAsyncBEFORE10241024Asynchronous94.13 μs0.259 μs0.731 μs94.02 μs92.27 μs96.16 μs1.005 KB
ReadAsyncAFTER10241024Asynchronous85.48 μs0.842 μs2.291 μs85.11 μs82.58 μs92.09 μs0.915 KB
WriteAsyncBEFORE10241024Asynchronous292.63 μs5.659 μs15.869 μs290.59 μs264.25 μs338.98 μs1.005 KB
WriteAsyncAFTER10241024Asynchronous324.35 μs13.906 μs39.222 μs311.27 μs274.34 μs428.64 μs1.115 KB
FlushAsyncBEFORE1024?None4,606.81 μs46.736 μs133.341 μs4,633.92 μs4,374.04 μs4,952.63 μs1.00269 KB
FlushAsyncAFTER1024?None4,587.88 μs58.046 μs168.403 μs4,638.98 μs4,322.79 μs5,034.48 μs1.00269 KB
CopyToFileAsyncBEFORE1024?None474.57 μs5.497 μs15.141 μs474.66 μs440.31 μs520.31 μs1.005 KB
CopyToFileAsyncAFTER1024?None488.94 μs3.631 μs10.061 μs488.62 μs469.93 μs518.76 μs1.035 KB
FlushAsyncBEFORE1024?Asynchronous14,680.35 μs150.967 μs437.982 μs14,637.76 μs13,623.50 μs15,645.01 μs1.00301 KB
FlushAsyncAFTER1024?Asynchronous20,333.22 μs116.277 μs339.185 μs20,299.93 μs19,724.25 μs21,148.73 μs1.39261 KB
CopyToFileAsyncBEFORE1024?Asynchronous520.97 μs5.982 μs16.674 μs519.35 μs490.05 μs572.29 μs1.006 KB
CopyToFileAsyncAFTER1024?Asynchronous523.30 μs6.294 μs17.650 μs520.91 μs479.14 μs583.85 μs1.006 KB
ReadAsyncBEFORE1048576512None1,375.28 μs5.930 μs17.015 μs1,376.60 μs1,340.44 μs1,413.73 μs1.0085 KB
ReadAsyncAFTER1048576512None1,347.16 μs5.128 μs14.958 μs1,345.14 μs1,323.57 μs1,382.14 μs0.9885 KB
WriteAsyncBEFORE1048576512None3,530.03 μs38.149 μs108.222 μs3,521.78 μs3,299.91 μs3,823.63 μs1.0076 KB
WriteAsyncAFTER1048576512None3,473.85 μs47.943 μs134.438 μs3,469.00 μs3,222.90 μs3,832.17 μs0.9876 KB
ReadAsyncBEFORE1048576512Asynchronous3,278.98 μs30.616 μs87.844 μs3,257.98 μs3,151.46 μs3,507.69 μs1.0093 KB
ReadAsyncAFTER1048576512Asynchronous4,399.34 μs16.153 μs47.119 μs4,401.64 μs4,294.71 μs4,531.75 μs1.3483 KB
WriteAsyncBEFORE1048576512Asynchronous5,304.10 μs73.935 μs214.500 μs5,309.27 μs4,771.82 μs5,852.43 μs1.0085 KB
WriteAsyncAFTER1048576512Asynchronous6,506.15 μs94.189 μs271.756 μs6,505.61 μs6,042.59 μs7,132.02 μs1.2375 KB
ReadAsyncBEFORE10485764096None1,090.98 μs6.156 μs18.152 μs1,096.14 μs1,039.20 μs1,120.19 μs1.0029 KB
ReadAsyncAFTER10485764096None1,084.03 μs8.601 μs25.225 μs1,086.13 μs1,042.07 μs1,131.85 μs0.9929 KB
WriteAsyncBEFORE10485764096None3,247.60 μs43.259 μs124.117 μs3,243.69 μs2,954.42 μs3,556.50 μs1.0055 KB
WriteAsyncAFTER10485764096None3,250.35 μs36.664 μs100.985 μs3,234.77 μs3,039.74 μs3,505.95 μs1.0055 KB
ReadAsyncBEFORE10485764096Asynchronous3,273.61 μs17.591 μs50.753 μs3,264.72 μs3,190.18 μs3,410.97 μs1.0079 KB
ReadAsyncAFTER10485764096Asynchronous4,547.38 μs73.438 μs216.535 μs4,398.34 μs4,327.10 μs5,035.98 μs1.3969 KB
WriteAsyncBEFORE10485764096Asynchronous5,384.46 μs83.792 μs240.415 μs5,346.69 μs4,897.35 μs5,955.92 μs1.0084 KB
WriteAsyncAFTER10485764096Asynchronous6,845.63 μs88.794 μs256.192 μs6,883.75 μs6,079.92 μs7,286.73 μs1.2774 KB
ReadAsync_NoBufferingBEFORE104857616384None406.34 μs2.413 μs7.113 μs403.28 μs387.16 μs419.08 μs1.007 KB
ReadAsync_NoBufferingAFTER104857616384None380.35 μs3.413 μs10.063 μs378.36 μs363.73 μs402.82 μs0.947 KB
WriteAsync_NoBufferingBEFORE104857616384None2,545.17 μs30.174 μs83.613 μs2,526.42 μs2,397.67 μs2,776.47 μs1.007 KB
WriteAsync_NoBufferingAFTER104857616384None2,537.77 μs27.489 μs76.628 μs2,528.02 μs2,421.65 μs2,750.68 μs1.007 KB
ReadAsync_NoBufferingBEFORE104857616384Asynchronous949.54 μs6.336 μs18.077 μs943.69 μs926.29 μs1,002.20 μs1.0020 KB
ReadAsync_NoBufferingAFTER104857616384Asynchronous1,195.21 μs3.070 μs8.507 μs1,194.94 μs1,179.45 μs1,218.92 μs1.2617 KB
WriteAsync_NoBufferingBEFORE104857616384Asynchronous2,965.91 μs40.616 μs114.558 μs2,952.10 μs2,677.93 μs3,244.55 μs1.0020 KB
WriteAsync_NoBufferingAFTER104857616384Asynchronous3,021.90 μs40.697 μs116.110 μs3,000.74 μs2,803.04 μs3,343.84 μs1.0217 KB
CopyToFileAsyncBEFORE1048576?None2,440.43 μs17.056 μs49.754 μs2,437.81 μs2,343.18 μs2,576.37 μs1.003 KB
CopyToFileAsyncAFTER1048576?None2,421.23 μs15.376 μs43.869 μs2,413.43 μs2,350.26 μs2,521.45 μs0.993 KB
CopyToFileAsyncBEFORE1048576?Asynchronous2,878.14 μs16.682 μs47.863 μs2,876.69 μs2,771.42 μs3,001.49 μs1.004 KB
CopyToFileAsyncAFTER1048576?Asynchronous2,769.09 μs15.649 μs43.882 μs2,760.85 μs2,691.56 μs2,885.67 μs0.964 KB
ReadAsyncBEFORE1048576004096None115,657.20 μs352.576 μs1,039.577 μs115,639.45 μs113,670.15 μs117,832.15 μs1.002,801 KB
ReadAsyncAFTER1048576004096None116,612.88 μs249.577 μs731.966 μs116,751.05 μs114,615.00 μs117,903.05 μs1.012,801 KB
WriteAsyncBEFORE1048576004096None205,378.59 μs7,785.258 μs22,462.248 μs194,584.50 μs184,125.30 μs268,874.60 μs1.005,005 KB
WriteAsyncAFTER1048576004096None209,453.56 μs10,355.532 μs30,043.276 μs194,064.20 μs173,778.40 μs293,593.30 μs1.035,005 KB
ReadAsyncBEFORE1048576004096Asynchronous327,225.97 μs1,532.011 μs4,346.059 μs326,564.00 μs320,187.10 μs339,508.20 μs1.007,801 KB
ReadAsyncAFTER1048576004096Asynchronous457,654.89 μs902.286 μs2,559.634 μs457,207.00 μs451,652.60 μs465,093.00 μs1.406,801 KB
WriteAsyncBEFORE1048576004096Asynchronous454,994.00 μs7,661.181 μs21,857.780 μs449,253.50 μs421,512.80 μs511,199.10 μs1.007,905 KB
WriteAsyncAFTER1048576004096Asynchronous600,076.62 μs10,088.501 μs29,268.570 μs592,035.20 μs559,265.50 μs677,403.80 μs1.326,905 KB
ReadAsync_NoBufferingBEFORE10485760016384None45,086.23 μs156.169 μs453.074 μs45,005.65 μs43,497.93 μs46,041.93 μs1.00701 KB
ReadAsync_NoBufferingAFTER10485760016384None42,133.53 μs174.922 μs457.740 μs42,093.18 μs41,187.75 μs43,731.75 μs0.93701 KB
WriteAsync_NoBufferingBEFORE10485760016384None61,814.53 μs685.277 μs1,887.453 μs61,419.69 μs59,079.38 μs68,035.43 μs1.00701 KB
WriteAsync_NoBufferingAFTER10485760016384None61,435.05 μs720.962 μs1,973.622 μs61,127.18 μs58,506.18 μs67,827.25 μs0.99701 KB
ReadAsync_NoBufferingBEFORE10485760016384Asynchronous97,220.19 μs667.147 μs1,881.701 μs96,464.10 μs94,386.65 μs102,972.80 μs1.001,951 KB
ReadAsync_NoBufferingAFTER10485760016384Asynchronous128,169.40 μs556.512 μs1,587.760 μs127,771.80 μs125,455.30 μs133,061.15 μs1.321,701 KB
WriteAsync_NoBufferingBEFORE10485760016384Asynchronous136,593.81 μs1,249.681 μs3,462.860 μs136,822.00 μs127,534.30 μs146,281.60 μs1.001,951 KB
WriteAsync_NoBufferingAFTER10485760016384Asynchronous169,656.11 μs2,245.877 μs6,515.696 μs168,284.30 μs157,404.20 μs189,342.10 μs1.251,701 KB
CopyToFileAsyncBEFORE104857600?None56,537.38 μs245.101 μs695.312 μs56,440.93 μs55,567.65 μs58,581.80 μs1.00177 KB
CopyToFileAsyncAFTER104857600?None56,184.32 μs275.348 μs785.585 μs56,116.43 μs54,790.00 μs58,574.72 μs0.99177 KB
CopyToFileAsyncBEFORE104857600?Asynchronous87,668.39 μs312.004 μs905.181 μs87,555.38 μs85,324.65 μs89,838.85 μs1.00246 KB
CopyToFileAsyncAFTER104857600?Asynchronous78,490.65 μs501.805 μs1,431.678 μs78,350.84 μs75,379.32 μs82,799.10 μs0.90214 KB

…Source only needed by Net5CompatFileStreamStrategy.
… one allocation and get the actual profiling improvement.Rename FileStreamAwaitableProvider.
… review. Shorter filename for awaitable provider.
…trategy, and its _fileHandle can now be private.
@adamsitnik
Copy link
Member

Most results had allocation improvements, but worse speed when FileOptions = Asynchronous:

As discussed offline, such a regression needs to be solved before merging the PR.

I've even built your fork and run the benchmarks myself to be 100% sure, but unfortunately, I've confirmed the regression:

MethodToolchainfileSizeuserBufferSizeoptionsMeanRatioAllocated
ReadAsync_NoBuffering\after\corerun.exe104857616384Asynchronous838.1 us1.1217 KB
ReadAsync_NoBuffering\before\corerun.exe104857616384Asynchronous748.1 us1.0020 KB
ReadAsync_NoBuffering\after\corerun.exe10485760016384Asynchronous93,214.6 us1.121,700 KB
ReadAsync_NoBuffering\before\corerun.exe10485760016384Asynchronous83,445.4 us1.001,950 KB

If I can suggest something,ReadAsync_NoBuffering withfileSize=1048576 andoptions=Asynchronous should be the best benchmark for creating a repo app and profiling it. It's the simplest benchmark (buffering is not involved) andReadAsync has a smaller deviation thanWriteAsync. If you fail to find the regression with VS Profiler, you can usePerfview.

carlossanlop reacted with thumbs up emoji

@adamsitnikadamsitnik added the NO-MERGEThe PR is not ready for merge yet (see discussion for detailed reasons) labelApr 14, 2021
@carlossanlop
Copy link
ContributorAuthor

I compared the CPU usage before and after, and discovered we spend a considerable amount of time insideOverlapped.PerformIOCompletionCallback.

The new code (left) consumes 62% of the time callingExecutionContext.RunInternal, while the old code (right) only took 3.82%.

Screenshot

image

The root cause seems to be this line, where we call_source.SetResult:

Screenshot

image

Which is adding up the time it takes to run the rest of theReadAsync calls in the loop, including the disposal of theFileStream object in the main method.

There's a comment stating thatExecutionContext.RunInternal (part of the callstack) warning that the code is a very hot path:

Screenshot

image

I wasn't expecting all the continuation code to be counted as part of the asynchronous calls. I wonder if this has anything to do with the removal ofRunContinuationsAsynchronously = true flag. I'll bring that flag back and compare the results.

cc@jozkee@adamsitnik@stephentoub

@davidfowl
Copy link
Member

We don't need to restore the ExecutionContext in the overlapped, that restore should be avoided in these APIs. The async state machine already does it. This is what you need#42549 😄

carlossanlop reacted with eyes emoji

@stephentoub
Copy link
Member

stephentoub commentedApr 14, 2021
edited
Loading

The new code (left) consumes 62% of the time calling ExecutionContext.RunInternal, while the old code (right) only took 3.82%.

That's inclusive (what "Total" means in the VS view). The callback is actually running the continuation now (because of RunContinuationsAsynchronously=false), rather than queueing it to be run on a different thread, so the 62% is inclusive of the actual app code.

adamsitnik reacted with thumbs up emoji

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I know you're copying this logic from what was there before, but it seems to be unnecessarily complex, with lots of interlocked operations for transitioning from one state to another to another. If we're looking for places to reduce overheads, revisiting this whole implementation is likely a good place to start.

carlossanlop and adamsitnik reacted with thumbs up emoji
@adamsitnik
Copy link
Member

The callback is actually running the continuation now (because of RunContinuationsAsynchronously=false)

I've run all the async benchmarks for 3 configurations:

  • "before" (@carlossanlop fork main branch)
  • "false" (the current state of this PR asRunContinuationsAsynchronously is not set so it'sfalse)
  • "true" ( _source.RunContinuationsAsynchronously = true; inctor)

With the following settings:

  • 100 instead of 20 iterations (--minIterationCount 100 --maxIterationCount 101)
  • no outliers removal (--outliers DontRemove)

and confirmed that with _source.RunContinuationsAsynchronously = true; we have no regression in themicrobenchmarks

@stephentoub should we:

  • setRunContinuationsAsynchronously = true; and include this PR in preview4
  • get some "real world" numbers from ASP.NET benchmarks (if there are none, write some and contribute tohttps://github.com/aspnet/benchmarks) and see howRunContinuationsAsynchronously setting affects non-micro benchmarks. This means preview5 for this PR.
MethodToolchainfileSizeuserBufferSizeoptionsMeanStdDevMedianMinMaxRatioAllocated
ReadAsync\before\corerun.exe10241024Asynchronous82.69 us1.946 us82.22 us81.60 us94.90 us1.005 KB
ReadAsync\false\corerun.exe10241024Asynchronous78.76 us0.718 us78.56 us78.04 us82.13 us0.955 KB
ReadAsync\true\corerun.exe10241024Asynchronous83.28 us0.825 us83.12 us82.48 us89.82 us1.015 KB
WriteAsync\before\corerun.exe10241024Asynchronous404.05 us22.574 us400.81 us379.20 us494.53 us1.005 KB
WriteAsync\false\corerun.exe10241024Asynchronous400.76 us17.211 us397.80 us380.07 us472.21 us0.995 KB
WriteAsync\true\corerun.exe10241024Asynchronous405.20 us25.469 us401.47 us383.59 us510.74 us1.015 KB
ReadAsync\before\corerun.exe1048576512Asynchronous2,418.83 us55.684 us2,413.59 us2,361.95 us2,804.46 us1.0093 KB
ReadAsync\false\corerun.exe1048576512Asynchronous2,648.89 us73.366 us2,626.45 us2,550.29 us2,959.92 us1.1083 KB
ReadAsync\true\corerun.exe1048576512Asynchronous2,386.25 us9.985 us2,386.16 us2,356.79 us2,419.58 us0.9983 KB
WriteAsync\before\corerun.exe1048576512Asynchronous4,254.70 us277.778 us4,213.63 us3,967.55 us6,203.71 us1.0085 KB
WriteAsync\false\corerun.exe1048576512Asynchronous4,583.08 us183.949 us4,559.42 us4,317.29 us5,334.19 us1.0875 KB
WriteAsync\true\corerun.exe1048576512Asynchronous4,207.77 us179.340 us4,173.53 us3,896.65 us4,865.02 us0.9975 KB
ReadAsync\before\corerun.exe10485764096Asynchronous2,343.06 us35.339 us2,355.62 us2,225.73 us2,379.72 us1.0079 KB
ReadAsync\false\corerun.exe10485764096Asynchronous2,603.91 us40.916 us2,603.11 us2,513.55 us2,715.90 us1.1169 KB
ReadAsync\true\corerun.exe10485764096Asynchronous2,324.68 us38.225 us2,337.29 us2,226.14 us2,374.04 us0.9969 KB
WriteAsync\before\corerun.exe10485764096Asynchronous4,110.48 us265.902 us4,050.53 us3,844.25 us6,007.74 us1.0084 KB
WriteAsync\false\corerun.exe10485764096Asynchronous4,572.16 us320.658 us4,542.09 us4,256.30 us7,440.25 us1.1274 KB
WriteAsync\true\corerun.exe10485764096Asynchronous4,048.01 us184.553 us4,015.01 us3,739.28 us4,754.61 us0.9974 KB
ReadAsync_NoBuffering\before\corerun.exe104857616384Asynchronous747.53 us5.076 us747.87 us729.80 us755.69 us1.0020 KB
ReadAsync_NoBuffering\false\corerun.exe104857616384Asynchronous822.87 us6.877 us822.90 us809.94 us838.18 us1.1017 KB
ReadAsync_NoBuffering\true\corerun.exe104857616384Asynchronous726.82 us11.262 us726.44 us699.96 us754.36 us0.9717 KB
WriteAsync_NoBuffering\before\corerun.exe104857616384Asynchronous2,726.54 us136.755 us2,712.48 us2,534.40 us3,234.12 us1.0020 KB
WriteAsync_NoBuffering\false\corerun.exe104857616384Asynchronous2,761.05 us146.361 us2,792.11 us2,507.70 us3,188.16 us1.0117 KB
WriteAsync_NoBuffering\true\corerun.exe104857616384Asynchronous2,736.46 us194.495 us2,705.98 us2,415.11 us4,022.13 us1.0117 KB
ReadAsync\before\corerun.exe1048576004096Asynchronous250,490.85 us2,604.707 us250,256.45 us244,814.30 us256,607.90 us1.007,801 KB
ReadAsync\false\corerun.exe1048576004096Asynchronous278,260.89 us4,931.050 us278,009.45 us269,882.60 us297,338.40 us1.116,803 KB
ReadAsync\true\corerun.exe1048576004096Asynchronous252,682.05 us19,763.442 us250,702.50 us243,911.20 us447,534.10 us1.016,801 KB
WriteAsync\before\corerun.exe1048576004096Asynchronous370,893.59 us6,590.799 us369,781.05 us352,834.20 us390,366.40 us1.007,905 KB
WriteAsync\false\corerun.exe1048576004096Asynchronous397,619.03 us8,020.098 us397,052.95 us384,609.00 us446,559.70 us1.076,905 KB
WriteAsync\true\corerun.exe1048576004096Asynchronous358,128.05 us6,536.171 us357,755.45 us346,707.00 us382,735.70 us0.976,905 KB
jozkee and carlossanlop reacted with thumbs up emoji

@adamsitnikadamsitnik removed the NO-MERGEThe PR is not ready for merge yet (see discussion for detailed reasons) labelApr 15, 2021
Copy link
Member

@adamsitnikadamsitnik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

LGTM, thank you@carlossanlop !

@carlossanlop
Copy link
ContributorAuthor

With the unit tests passing, the micro benchmarks having no regressions after revertingRunContinuationsAsynchronously totrue, and having allocation improvements, I feel confident in merging this change to include it in Preview4, and will continue working on#50972 to add caching and improve this code for Preview5.

adamsitnik reacted with thumbs up emoji

@carlossanlopcarlossanlop merged commita38d0c2 intodotnet:mainApr 15, 2021
@carlossanlopcarlossanlop deleted the IValueTaskSource branchApril 15, 2021 22:51
@ghostghost locked asresolvedand limited conversation to collaboratorsMay 15, 2021
Sign up for freeto subscribe to this conversation on GitHub. Already have an account?Sign in.

Reviewers

@stephentoubstephentoubstephentoub left review comments

@adamsitnikadamsitnikadamsitnik approved these changes

@jozkeejozkeejozkee approved these changes

@jeffhandleyjeffhandleyAwaiting requested review from jeffhandley

Assignees

@carlossanlopcarlossanlop

Projects

None yet

Milestone

6.0.0

Development

Successfully merging this pull request may close these issues.

5 participants

@carlossanlop@davidfowl@stephentoub@adamsitnik@jozkee

[8]ページ先頭

©2009-2025 Movatter.jp