-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove FileStream long pinning, and simplify synchronization #51462
Conversation
Tagging subscribers to this area: @carlossanlop Issue DetailsThis PR has two changes for FileStream... The first is to remove the permanent pinning performed by BufferedFileStreamStrategy. Previously, on Windows when FileStream would allocate its buffer, it would also pin it as part of creating a PreallocatedOverlapped. Recent changes removed the PreallocatedOverlapped wrapping of the buffer, and replaced it with a buffer from the pinned-object heap. But POH allocations are considered to be gen2, which can then have a negative app-wide impact if lots of FileStreams are created and thrown away quickly. Instead, this PR just removes that pinning entirely. BufferedFileStreamStrategy's buffer, like any other buffer, will just be pinned for the duration of each individual read/write async operation, using Memory.Pin. I do not see any degradation in the benchmarks as a result. cc: @jkotas The second change is to significantly simplify how the async completion is handled. There's currently a complicated set of interlocked operations that transition the promise through multiple stages, to account for the possibility of race conditions between various operations. We can instead get rid of all of that, and just use an event to handle the rare case where additional configuration of an async operation after that async operation was just initiated (e.g. registering for cancellation) doesn't race with concurrent completion of that same operation. @adamsitnik, I don't see regressions from these changes, but the benchmarks also appear to be quite noisy, at least on my machine. If you could give them a go, that'd be helpful. Thanks.
|
0547102
to
f3eafd7
Compare
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/BufferedFileStreamStrategy.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/BufferedFileStreamStrategy.cs
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs
Show resolved
Hide resolved
...m.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.ValueTaskSource.cs
Show resolved
Hide resolved
...m.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.ValueTaskSource.cs
Outdated
Show resolved
Hide resolved
...m.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.ValueTaskSource.cs
Outdated
Show resolved
Hide resolved
There are no regressions and the number of Gen 2 collections have dropped (as expected):
Some of the benchmarks (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good so far. Pending answering the dispose question and benchmark results cc @adamsitnik
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/BufferedFileStreamStrategy.cs
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/BufferedFileStreamStrategy.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs
Show resolved
Hide resolved
f3eafd7
to
3225998
Compare
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thank you @stephentoub !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. The only comment is that I think having a Dispose
method without implementing IDisposable
seems a bit weird, but I don't think it is a merge blocker.
@stephentoub should we backport this PR to the preview4 branch? My main concern are the arrays allocated from the pinned heap and the Gen 2 collections that can cause. |
I don't think it meets the bar. |
This PR has two changes for FileStream...
The first is to remove the permanent pinning performed by BufferedFileStreamStrategy. Previously, on Windows when FileStream would allocate its buffer, it would also pin it as part of creating a PreallocatedOverlapped. Recent changes removed the PreallocatedOverlapped wrapping of the buffer, and replaced it with a buffer from the pinned-object heap. But POH allocations are considered to be gen2, which can then have a negative app-wide impact if lots of FileStreams are created and thrown away quickly. Instead, this PR just removes that pinning entirely. BufferedFileStreamStrategy's buffer, like any other buffer, will just be pinned for the duration of each individual read/write async operation, using Memory.Pin. I do not see any degradation in the benchmarks as a result. cc: @jkotas
The second change is to significantly simplify how the async completion is handled. There's currently a complicated set of interlocked operations that transition the promise through multiple stages, to account for the possibility of race conditions between various operations. We can instead get rid of all of that, and just use an event to handle the rare case where additional configuration of an async operation after that async operation was just initiated (e.g. registering for cancellation) doesn't race with concurrent completion of that same operation.
@adamsitnik, I don't see regressions from these changes, but the benchmarks also appear to be quite noisy, at least on my machine. If you could give them a go, that'd be helpful. Thanks.