This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Several Stream.Read/WriteAsync improvements #2724
Merged
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Stream.XxAsync are currently implemented as a wrapper around Stream.Begin/EndXx. When a derived class overrides XxAsync to do its own async implementation, there's no issue. When a derived class overrides Begin/EndXx but not XxAsync, there's no issue (the base implementation does what it needs to do, wrapping Begin/EndXx). However, if the derived implementation doesn't override either XxAsync or Begin/EndXx, there are a few issues. First, there's unnecessary cost. The base Begin/EndXx methods queue a Task to call the corresponding Read/Write method. But then the XxAsync method create another Task to wrap the Begin/End method invocation. This means we're allocating and completing two tasks, when we only needed to do one. Second, task wait inlining is affected. Because Read/WriteAsync aren't returning the original queued delegate-backed task but rather a promise task, Wait()'ing on the returned task blocks until the operation completes on another thread. If the original delegate-backed task were returned, then Wait()'ing on the task could potentially "inline" its execution onto the current thread. Third, there's unnecessary blocking if there are other outstanding async operations on the instance. Since Begin/EndXx were introduced, they track whether an operation is in progress, and subsequent calls to BeginXx while an operation is in progress blocks synchronously. Since Read/WriteAsync just wrap the virtual Begin/End methods, they inherit this behavior. This commit addresses all three issues for CoreCLR. The Begin/EndXx methods aren't exposed from Stream in the new contracts, and as a result outside of mscorlib we don't need to be concerned about these methods being overridden. Thus, the commit adds an optimized path that simply returns the original delegate-backed task rather than wrapping it. This avoids the unnecessary task overheads and duplication, and it enables wait inlining if someone happens to wait on it. Further, since we're no longer subject to the behaviors of Begin/End, we can change the serialization to be done asynchronously rather than synchronously.
- Remove older reflection-based detection of overrides from SyncStream - Remove Tuple allocation from RunReadWriteTaskWhenReady - Fix spelling of Overridden - Add a few comments
stephentoub
force-pushed
the
readwrite_perf
branch
from
January 19, 2016 15:52
bd8b03e
to
22c5cb0
Compare
5 tasks
LGTM |
LGTM. I'd be interested in seeing something like this on desktop. We could use similar FCalls to detect a stream has only overridden TPL methods and have the APM methods call the TPL methods rather than running the sync methods on a background thread. |
jkotas
added a commit
that referenced
this pull request
Jan 19, 2016
Several Stream.Read/WriteAsync improvements
stephentoub
added a commit
to stephentoub/corefx
that referenced
this pull request
Jan 19, 2016
A logical port of two of the three fixes in dotnet/coreclr#2724. Makes it so that Read/WriteAsync calls are serialized asynchronously rather than synchronously, and so that Wait()'ing on a Read/WriteAsync task may be able to inline the execution onto the current thread. This also reduces allocations when calling Read/WriteAsync (though there are still more optimizations that could be done).
if (actual == base) | ||
return false; | ||
|
||
if (!g_pStreamMT->IsZapped()) |
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.
IsZapped()? @jkotas what's the history behind that naming?
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.
zap
was the codename for ngen when it was built for .NET Framework v1.0 originally. E.g. src\zap
is where most of the ngen compiler specific code lives.
5 tasks
picenka21
pushed a commit
to picenka21/runtime
that referenced
this pull request
Feb 18, 2022
A logical port of two of the three fixes in dotnet/coreclr#2724. Makes it so that Read/WriteAsync calls are serialized asynchronously rather than synchronously, and so that Wait()'ing on a Read/WriteAsync task may be able to inline the execution onto the current thread. This also reduces allocations when calling Read/WriteAsync (though there are still more optimizations that could be done). Commit migrated from dotnet/corefx@556f7e2
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Stream.XxAsync are currently implemented as wrappers around Stream.Begin/EndXx. When a derived class overrides XxAsync to do its own async implementation, there's no issue (its derived XxAsync implementation will be used). When a derived class overrides Begin/EndXx but not XxAsync, there's no issue (the base implementation does what it needs to do, wrapping Begin/EndXx). However, if the derived implementation doesn't override either XxAsync or Begin/EndXx, there are a few issues.
First, there's unnecessary cost. The base Begin/EndXx methods queue a Task to call the corresponding Read/Write method. But then the XxAsync methods create another Task to wrap the Begin/End methods invocation. This means we're allocating and completing two tasks, when we only needed to do one.
Second, task wait inlining is affected. Because Read/WriteAsync aren't returning the original queued delegate-backed task but rather a promise task, Wait()'ing on the returned task blocks until the operation completes on another thread. If the original delegate-backed task were returned, then Wait()'ing on the task could potentially "inline" its execution onto the current thread.
Third, there's unnecessary blocking if there are other outstanding async operations on the instance. Since the Begin/EndXx methods were introduced, they've tracked whether an operation is in progress, and subsequent calls to BeginXx while an operation is in progress blocks synchronously (this is because most Read/Write methods on most Streams are not safe to be called concurrently). Since Read/WriteAsync just wrap the virtual Begin/End methods, they inherit this behavior.
This commit addresses all three issues for CoreCLR. The Begin/EndXx methods aren't exposed from Stream in the new contracts, and as a result outside of mscorlib we don't need to be concerned about these methods being overridden. Thus, the commit adds an optimized path that simply returns the original delegate-backed task rather than wrapping it. This avoids the unnecessary task overheads and duplication, and it enables wait inlining if someone happens to wait on it. Further, since we're no longer subject to the behaviors of Begin/End, we can change the serialization to be done asynchronously rather than synchronously. Override detection is done via a new FCall added for this purpose by @jkotas (thanks!)
On a microbenchmark that measures the overhead of Stream.XxAsync when Begin/EndXx are not overridden, this change improves single-threaded throughput by ~25% and cuts allocations by ~50%.
cc: @jkotas, @janvorli, @ericstj, @ellismg, @AlfredoMS
Related to https://github.com/dotnet/coreclr/issues/2712, https://github.com/dotnet/corefx/issues/5077