Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Optimize overlapped I/O FileStream.CopyToAsync implementation on Windows #11569

Merged
merged 2 commits into from Sep 13, 2016

Conversation

stephentoub
Copy link
Member

Add an override of CopyToAsync in the Windows implementation of FileStream for use when the stream is constructed in async mode.

The base stream implementation does a simple loop that reads from the source and writes to the destination. For the Windows implementation of FileStream in async mode, each of these read operations involves overhead, such as allocating the task to return from the operation, registering with the providing cancellation token, etc. For CopyToAsync, we’re responsible for all of the reads, which means we can avoid these per-read costs.

Copying a 10MB file to a MemoryStream with the default buffer size and with a cancelable token improved throughput by 50%, and (not including the copy buffer) reduced the number of allocations from 860 to 11 and the bytes allocated from 52K to ~730b. Copying a 100 byte file to a MemoryStream with the default buffer size and with a non-cancelable token improved throughput by 30% and (not including the copy buffer) reduced the number of allocations from 46 to 11 and the bytes allocated from 1.1K to ~670b.

(I briefly explored adding an override for when in sync mode, but the savings there aren’t nearly as significant or measurable. At best it can avoid a Task per read. We can look more seriously at doing that separately, if desired; that could likely also be done for the Unix implementation.)

Fixes #11539
cc: @ericstj, @JeremyKuhne, @ianhays, @davidfowl

// whatever read operation may currently be in progress, if there is one. It's possible the cancellation
// request could come in between operations, in which case we flag that with explicit calls to ThrowIfCancellationRequested
// in the read/write copy loop.
cancellationReg = cancellationToken.Register(s =>
Copy link
Member

Choose a reason for hiding this comment

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

Can you just avoid this if cancellationToken = None?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I'll do that, though it won't save very much: one of the first things Register does is check that (or, rather, CanBeCanceled), and exits early if it can't.

@davidfowl
Copy link
Member

LGTM

@stephentoub stephentoub force-pushed the filestream_copytoasync branch 2 times, most recently from 119c770 to 93e928e Compare September 10, 2016 20:53
unsafe
{
lock (innerAwaitable.CancellationLock) // synchronize with cleanup of the overlapped
{
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: indentation

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

@stephentoub stephentoub force-pushed the filestream_copytoasync branch 4 times, most recently from bd71bdb to 5b4ece3 Compare September 11, 2016 13:23
@stephentoub
Copy link
Member Author

I've fixed the issue that was causing the CI runs to hang.

@davidfowl
Copy link
Member

😄 Waiting for this change

@@ -1684,6 +1685,299 @@ private int GetLastWin32ErrorAndDisposeHandleIfInvalid(bool throwIfInvalidHandle
return errorCode;
}

public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
{
// Validate arguments as would the base implementation
Copy link
Member

Choose a reason for hiding this comment

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

These lines hurt my eyes. Is this really in line with our style guidelines?

Copy link
Member Author

Choose a reason for hiding this comment

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

Is this really in line with our style guidelines?

We allow for single lines like this. I find this easier to follow than tripling the size of the arg validation by moving the bodies to their own lines and surrounding by braces on their own lines. But I don't feel strongly about it and can change it.

Copy link
Member

Choose a reason for hiding this comment

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

My preference is to match the pattern in the rest of the file.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok.

@JeremyKuhne
Copy link
Member

lgtm, thanks

Add an override of CopyToAsync in the Windows implementation of FileStream for use when the stream is constructed in async mode.

The base stream implementation does a simple loop that reads from the source and writes to the destination.  For the Windows implementation of FileStream in async mode, each of these read operations involves overhead, such as allocating the task to return from the operation, registering with the providing cancellation token, etc.  For CopyToAsync, we’re responsible for all of the reads, which means we can avoid these per-read costs.

Copying a 10MB file to a MemoryStream with the default buffer size and with a cancelable token improved throughput by 50%, and (not including the copy buffer) reduced the number of allocations from 860 to 11 and the bytes allocated from 52K to ~730b. Copying a 100 byte file to a MemoryStream with the default buffer size and with a non-cancelable token improved throughput by 30% and (not including the copy buffer) reduced the number of allocations from 46 to 11 and the bytes allocated from 1.1K to ~670b.

(I briefly explored adding an override for when in sync mode, but the savings there aren’t nearly as significant or measurable.  At best it can avoid a Task per read.  We can look more seriously at doing that separately, if desired; that could likely also be done for the Unix implementation.)
@stephentoub stephentoub merged commit f3a2e16 into dotnet:master Sep 13, 2016
@stephentoub stephentoub deleted the filestream_copytoasync branch September 13, 2016 01:49
@karelz karelz modified the milestone: 1.2.0 Dec 3, 2016
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
…pytoasync

Optimize overlapped I/O FileStream.CopyToAsync implementation on Windows

Commit migrated from dotnet/corefx@f3a2e16
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
8 participants