Windows:基于事件的重叠IO与IO完成端口,现实世界性能

12

我正在构建一个服务器应用,并研究使用重叠IO来处理套接字。我发现有些人说“永远不要使用hEvent”,或者“IO完成端口会更快”等等,但是没有人解释为什么不能使用hEvent,也没有提供任何有关完成端口速度更快的真实数据或数字,以及相差多少。

hEventWaitForMultipleObjects()结合更适合我的应用程序,因此如果速度差异微不足道,我倾向于使用它,但我不想在没有真实数据告诉我所做的取舍有多大时就进行决策。我已经搜索了很多次,但无法找到任何比较这两种策略的基准测试或文章或其他东西,除了一些StackOverflow答案说“不要使用这个”而没有给出理由。

是否有人可以向我提供一些关于使用hEvent和完成端口之间实际世界差别的真实信息或数字呢?


这是关于减少线程数量的问题。如果您使用单个WaitForMultipleObjects等待所有I/O,那么基本上与I/O完成端口相同。您不希望每个I/O操作都带有自己的WaitForSingleObject,因为这意味着每个挂起的I/O操作都需要自己的线程,这不可扩展 - Raymond Chen
我完全理解WaitForSingleObject绝对不会扩展的情况。在这种情况下,我想象WaitForMultipleObjects扮演类似于epoll的角色,尽管我意识到存在很大的差异。我也能理解完成端口可能比WaitForMultipleObjects略有优势,但如果优势很小,我更喜欢采用WaitForMultipleObjects方法,因为它可以更好地(感知)控制应用程序中的线程。 - ShadauxCat
5
请注意,使用WaitForMultipleObjects会限制每个线程的客户端数为64。 你预计将有多少个同时连接? - Harry Johnston
那些说“永远不要使用hEvent”的人需要给出一个理由,否则这个建议是没有用的。 - Raymond Chen
2
@HarryJohnston 真的吗?MAXIMUM_WAIT_OBJECTS 就是这样子的?这真是一个非常好的理由不去使用它 - 我实际上还没有开始写代码,只是看了文档,并且认为 MAXIMUM_WAIT_OBJECTS 会是...某种合理的东西。谢谢你终于给出一个好理由!(我刚刚明显地表明了我不是主要的Windows开发者。 :) ) - ShadauxCat
3个回答

10
这个答案最初来自Harry Johnston在问题上的评论,通过一些搜索,我发现了一些更多的细节,使得WaitForMultipleObjects成为一件可怕的事情。
你可以等待的对象的最大数量是64。这就使得WFMO方法的可扩展性基本不存在。但是进一步观察,我找到了这个线程:https://groups.google.com/forum/#!topic/comp.os.ms-windows.programmer.win32/okwnsYetF6g 在NT术语中,要进入等待状态,必须为每个对象分配一个等待块,然后将每个等待块排队到您正在等待的对象,并将其交叉链接到线程。当这些对象中的任何一个被标记时,所有这些等待块都必须被出列、取消链接并退回到池中。所有这些都发生在DISPATCH_LEVEL级别,除了池分配和释放之外,其他所有操作都会保持调度程序自旋锁。 (fAll == TRUE的WFMO甚至更加昂贵。每次标记任何一个对象时,都必须检查所有其他对象。这一切都发生在DISPATCH_LEVEL,而且还保持着调度程序自旋锁。)
调度程序级别的自旋锁防止了整个系统中线程的抢占和时间分片,即使有多个核心。如果你正在等待超过3个对象(该线程预先分配了3个等待块并且可以避免大量这种情况),那么这真是可怕的,也是永远不要使用WFMO的一个很好的理由。

8
自从Windows 7之后,调度程序自旋锁已经不存在了,因此对于调度程序自旋锁的担忧不再适用。 - Raymond Chen
好知道。虽然很多人仍在使用Windows 7,但仍值得注意。 - ShadauxCat
5
Windows 7 已经修复了,而 Windows Vista 仍存在调度程序锁定问题。 - Raymond Chen
啊,好的,抱歉。我误解了。 :) - ShadauxCat

4
为了获得最佳性能,您应该使用IO完成端口。没有套接字数量的限制。所有其他类似于select的API都只能服务于1024个套接字,并且性能会迅速下降,同时CPU使用率也会高于所需水平。

https://msdn.microsoft.com/en-us/library/windows/desktop/aa365198(v=vs.85).aspx

您也可以查看这个关于异步I/O的精彩演示,我认为对于任何考虑编写中大型客户端服务器应用程序的人来说,这都是必看的。

时间的历史:异步C++ - Steven Simpson [ACCU 2017] https://www.youtube.com/watch?v=Z8tbjyZFAVQ

在这个演示中,您将找到可用技术的完整描述和比较,以及基准测试结果。非常值得一看。

WaitForMultipleObjects()对于处理涉及多个I/O流的任何内容都不实用,因为它限制了最多只能处理64个句柄。


当然,选择类似的API存在问题,但我没有询问它们。 :) 我已经意识到select()、poll()等存在的问题。这个问题特别涉及与重叠IO交互的两种方式(都是异步的、非阻塞的,并且不使用类似于select的API):IO完成端口与在OVERLAPPED结构上设置hEvent对象。我感谢您提供的信息——但您实际上并没有在这里谈论WaitForMultipleObjects... - ShadauxCat
我看到其他网站的说法与此不同,但没有人给出任何实际原因或实际数据。我看到有些网站说“重叠IO是最好的(除了完成端口,它们更好)”。然后我看到其他人说“不要使用'WFMO'”。如果你阅读我的帖子内容,我的问题不是“我应该使用哪个”,而是“有人能给我一些真实的数字和原因来说明一个比另一个更好吗?”正如Raymond Chen所说,没有给出理由就说“不要使用这个”并没有什么特别有用的建议。(话虽如此,如果你看看我的答案,我找到了原因。) - ShadauxCat
2
换句话说,这个问题并不是在寻求建议,而是在寻求理解。我知道人们建议使用哪一个,但我想知道为什么,以及差别有多大。关于IOCPselect()之间的比较很多,但是没有关于IOCP和其他重叠IO工作方法的比较。我想要实际数据,以便能够做出自己的明智结论,而不是假设互联网是正确的。 - ShadauxCat
上面链接的视频正是如此。在C++环境中详细介绍了这一点。 - Michaël Roy
1
它没有讨论WaitForMultipleObjects,因为它甚至都不被认为是可行的解决方案。正如每个人告诉你的那样。由于这两个API访问相同的底层技术,尽管针对不同的操作系统,因此epoll和io完成端口的性能是可比较的。 - Michaël Roy
显示剩余4条评论

0

实际上,还有第三种通知重叠 I/O 完成的方法,那就是使用一个重叠完成例程,该例程排队到启动线程的 APC 队列。

所有这些方法都足够有用。

基于事件的通知最大的缺点是它不可扩展(一次只能等待 64 个事件)。Win32 事件并不是获取通知的最佳方式。这适用于做很少 I/O 但不想在启动线程上等待完成或想进行一些有限多路复用的多线程应用程序。

重叠完成例程的主要缺点是您无法控制哪个线程接收通知,并且需要将启动线程置于可警告的等待状态以运行完成例程。这适用于传统的单线程 UI 应用程序(使用 MWMOEx 等待消息并同时将主线程置于可警告的等待状态),但不适用于现代高质量的视频游戏或互联网服务,因为它们可能从任何一个线程启动 I/O 并执行大量 I/O。

IOCP覆盖了这样一种情况:您有许多可能启动I/O的线程,进行大量I/O和/或希望在任意线程(或实质上是任何可能的线程)上得到完成通知的情况。相对于其他选项,它唯一不适合的应用程序类型是单线程UI应用程序。

我没有在性能方面比较这三种方法,我只是认为它们适用于不同的模式。我从未自己使用过事件通知,但在各种项目中都使用过完成例程和IOCP,尽管说实话,我十年来实际上并没有使用过完成例程。


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