ReceiveAsync和BeginReceive的性能比较

11

我目前正在编写一个客户端应用程序,我想知道是否应该使用Socket类的ReceiveAsync方法还是BeginReceive方法。到目前为止,我一直在使用后者,但是我发现它似乎会给CPU带来很大的压力。以下是我的接收循环的基本样式:

private void socket_ReceiveCallback(IAsyncResult result_)
{
    // does nothing else at the moment
    socket.EndReceive(result_);
    byte[] buffer = (byte[])result_.AsyncState;

    // receive new packet
    byte[] newBuffer = new byte[1024];
    socket.BeginReceive(newBuffer, 0, newBuffer.Length, SocketFlags.None, 
                        socket_ReceiveFallback, newBuffer);
}

我一直在想我是否做错了什么,因为其他通信应用几乎不会过多地消耗 CPU。同时我在想,使用 SocketAsyncEventArgs 和 ReceiveAsync 是否会更好。

我的问题如下:

为什么我的循环会如此消耗 CPU? 我应该使用 SocketAsyncEventArgs 和 ReceiveAsync 替代 BeginReceive 吗?


我敢打赌这是因为你在一个循环中调用了你的方法,而且没有阻塞,所以你正在最大化使用其中一个核心 - 如果需要手动阻塞,请使用ManualResetEvent类。 - markmnl
3
“socket_ReceiveFallback”在您的实现中确实是一个不同的方法吗? - markmnl
4个回答

8
BeginReceiveEndReceive是旧的遗留异步模式的剩余物,它们在引入C# 5中的现代asyncawait关键字之前被使用。
因此,在异步编程中,您应该优先使用ReceiveAsync而不是BeginReceiveEndReceive
对于真正高性能的场景,您应该使用SocketAsyncEventArgs。这是为高性能而设计的,并且被Kestrel Web服务器使用。
SocketAsyncEventArgs文档的备注部分:

SocketAsyncEventArgs类是System.Net.Sockets.Socket类增强集合的一部分,提供了一种可由专用高性能套接字应用程序使用的替代异步模式。该类专门为需要高性能的网络服务器应用程序设计。应用程序可以专门使用增强的异步模式或仅在目标热点区域(例如,在接收大量数据时)中使用。

这些增强功能的主要特点是避免在高容量异步套接字I/O期间重复分配和同步对象。 System.Net.Sockets.Socket类当前实现的Begin/End设计模式需要为每个异步套接字操作分配System.IAsyncResult对象。

在新的System.Net.Sockets.Socket类增强功能中,应用程序通过可重用的SocketAsyncEventArgs对象描述异步套接字操作,并进行分配和维护。高性能套接字应用程序最了解必须维持的重叠套接字操作数量。应用程序可以创建所需的任意数量的SocketAsyncEventArgs对象。例如,如果服务器应用程序需要始终支持传入客户端连接速率的15个套接字接受操作,则可以为此目的分配15个可重用的SocketAsyncEventArgs对象。


我最近因为这个回答而进行了调查。看起来SocketAsyncEventArgs是为Silverlight而设计的。 由于Silverlight的寿命将于2021年结束,因此现在采用该技术时应该要谨慎。 - paulecoyote
@paulecoyote 我认为它还没有被淘汰。它仍然在 .NET 5 中可用,我认为 Kestrel 在使用它。 - Fred

7
我已经在本地循环连接上进行了同步和异步套接字基准测试。结果显示异步版本较慢,约慢了30%。这让我感到惊讶,因为异步IO现在很流行。无论我使用多少线程,情况都是一样的。我可以使用128个线程,同步IO仍然更快。
我认为,原因是异步IO需要更多的分配和内核模式转换。
因此,如果您不期望同时有数百个连接,请切换到同步IO。

1
我一定会对这个选项进行基准测试。我猜同步版本会更快。而且更易于维护! - usr
1
你尝试了多少并发连接?异步方法主要用于服务器应用程序。 - jgauffin
@usr:这是正确的。异步会导致上下文切换,增加了数千个额外的CPU周期。高带宽要求决定您应该使用同步模式,并在数据不立即可用时回退到异步。我们发现最好的实现是两阶段等待,其中我们使用SpinWait对象来检查数据可用性;当spinwait指示即将进行上下文切换(检查属性NextSpinWillYield)时,进行异步调用。 - Max
@John 还有一种选择,可以查询“int DataAvailable”以同步读取那么多数据,然后发出异步读取(可能是大小为1的异步读取)。我从未见过需要使用此技术来提高性能的情况,但我相信这样的地方存在。 - usr
答案是5年前写的。现在在.NET4.5中,任务得到了大幅改进(减少了内存占用等),这种情况仍然存在吗? - Riki
显示剩余5条评论

1
我进行了最大负载的比较,结果以GB(每秒千兆字节)为单位:

  • ReceiveAsync:约1.2GB
  • BeginReceive:约1.1GB
  • 在线程循环中接收:约1.4GB

注:

  • 所有结果都是使用回送地址(localhost)和发送套接字的线程进行的
  • 缓冲区大小为8192字节

对于大负载传输,建议使用在线程中的接收,但对于具有多个连接的更好CPU性能,则使用ReceiveAsync或BeginReceive。


1

要回答这个问题,您需要对应用程序进行分析。

  • 我想知道为什么我看不到EndReceive
  • 为什么您根本不使用接收到的缓冲区 和
  • 为什么您一次又一次地分配新缓冲区 - 这是此处唯一需要消耗任何资源(CPU/内存)的操作

看看这个:http://msdn.microsoft.com/de-de/library/dxkwh6zw.aspx


  1. 抱歉,忘记在代码中添加了。它已经在程序中了。我已经编辑了我的帖子。
  2. 我计划使用它。但是,即使不使用它,服务器也会大量占用CPU。
  3. 那么创建一个缓冲池是否更明智呢?我会这样做并测试是否有任何变化!感谢您的帮助。
- haiyyu
我目前最好的猜测是,由于您只传输小块(1kB),并且没有延迟 - 硬件没有太多等待时间来完成它的工作(如果在同一台机器上执行此操作,则更为真实),因此系统正在忙于创建异步调用。但我真的建议找到一个好的分析器(大多数都有试用版),甚至使用内置的(我认为VS prof或更高版本)来查找弱点。 - Random Dev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接