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

Rewrite Socket Task-based send/receive async operations on SocketAsyncEventArgs #16502

Merged
merged 3 commits into from
Mar 3, 2017

Conversation

stephentoub
Copy link
Member

@stephentoub stephentoub commented Feb 27, 2017

They're currently on top of the APM methods. This commit changes the ReceiveAsync/Send{To}Async operations to instead sit on top of a cached set of SocketAsyncEventArgs instances.

On a microbenchmark that connects two sockets and then does a million 1byte sends/receives from one to the other, top allocations before:
image
And top allocations after:
image

In a small change that forces all receives to be asynchronous by doing them before the send, top allocations before:
image
And top allocations after:
image
Throughput on the send/receive case also improved ~60%. Throughput on the receive/send case improved ~10%.

The worst case for this change is when only one send/receive is done per socket, since then it doesn't benefit from the SocketAsyncEventArgs caching. It takes two send/receives per socket to break even on allocation, and after that it's a definitive win.

With the task-based versions now generally much more efficient than the APM-based ones, I've switched NetworkStream.Read/WriteAsync to use them.

cc: @geoffkizer, @CIPop, @davidsh, @vancem

{
throw new ArgumentNullException(nameof(buffer.Array));
}
if (buffer.Offset < 0 || buffer.Offset > buffer.Array.Length)

Choose a reason for hiding this comment

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

I'm curious, are the checks for Offset and Count really useful? I'm not sure how you could get into this scenario without serious hackage (e.g. unsafe code). And if you're doing that, you could very easily hack the Array pointer to point into space. Do we gain anything by this?

Also, I notice we don't do the same validation in the BufferList case.

Copy link
Member Author

@stephentoub stephentoub Feb 27, 2017

Choose a reason for hiding this comment

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

Maybe not; I was just keeping the same semantics that were there already. If we don't think it's possible, we could subsequently delete them from here and everywhere else the same checks exist.

@geoffkizer
Copy link

LGTM. Awesome work here.

@stephentoub
Copy link
Member Author

Thanks for reviewing, @geoffkizer.

@stephentoub stephentoub force-pushed the socket_sendreceive_tasks branch from 0284351 to a5218ff Compare February 28, 2017 15:08
@stephentoub
Copy link
Member Author

@dotnet-bot test Outerloop Ubuntu14.04 Debug please (something's hanging in the Pipes tests)

@stephentoub stephentoub force-pushed the socket_sendreceive_tasks branch 2 times, most recently from 455f06f to 9a4fdbb Compare March 2, 2017 19:50
Changing the implementation of Read/WriteAsync surfaced some missing coverage.
…cEventArgs

They're currently on top of the APM methods.  This commit changes the ReceiveAsync/Send{To}Async operations to instead sit on top of a cached set of SocketAsyncEventArgs instances.  We only cache one instance for each of send/receive, so concurrent usage when the cached instance is already being used fall back to wrapping APM as before.
Prior to this change, NetworkStream.EndRead/Write would wrap SocketExceptions thrown from EndReceive/Send in an IOException, and thus Read/WriteAsync would propagate the IOException.  By changing Read/WriteAsync to delegate to ReceiveAsync/SendAsync instead, that wrapping was lost, so cases that previously threw IOException now may throw SocketException.  To address that, we could add a continuation to each Read/WriteAsync in case it fails, but that'd be adding allocations for the failure case, and they'd be pure overhead in the success case.  Instead, we pass a Boolean to the internal Read/WriteAsync helpers on Socket that indicates whether to just create a SocketException or to further wrap it in an IOException.  This adds minimal overhead while maintaining the same exception types.
@stephentoub stephentoub force-pushed the socket_sendreceive_tasks branch from 9a4fdbb to 14ae08d Compare March 2, 2017 22:09
@stephentoub
Copy link
Member Author

Everything passed, but I'm going to run all of the legs one more time.
@dotnet-bot test this please

@stephentoub
Copy link
Member Author

@dotnet-bot test Outerloop Windows_NT Release please
@dotnet-bot test Outerloop Ubuntu14.04 Debug please
@dotnet-bot test Outerloop OSX Debug please
@dotnet-bot test outerloop PortableLinux Debug
@dotnet-bot test outerloop OpenSUSE42.1 Release
@dotnet-bot test outerloop CentOS7.1 Debug
@dotnet-bot test outerloop RHEL7.2 Release

@stephentoub
Copy link
Member Author

@dotnet-bot test OuterLoop RHEL7.2 Release please (known HTTP failure)
@dotnet-bot test OuterLoop PortableLinux Debug please (known HTTP failure)
@dotnet-bot test OuterLoop CentOS7.1 Debug please (known HTTP failure)

@stephentoub
Copy link
Member Author

@dotnet-bot test OuterLoop CentOS7.1 Debug please (known HTTP failure)

@stephentoub stephentoub merged commit d851591 into dotnet:master Mar 3, 2017
@stephentoub stephentoub deleted the socket_sendreceive_tasks branch March 3, 2017 12:17
@karelz karelz modified the milestone: 2.0.0 Mar 7, 2017
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
…ceive_tasks

Rewrite Socket Task-based send/receive async operations on SocketAsyncEventArgs

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

Successfully merging this pull request may close these issues.

5 participants