CPU未得到充分利用。由于阻塞I / O?

时间:2013-11-02 23:32:45

标签: c# performance async-await msmq

我试图找到C#服务器应用程序瓶颈未充分利用的地方。我认为这可能是由于磁盘I / O性能不佳而与应用程序本身无关,但我无法从这个假设中得出一个事实。

应用程序从本地MSMQ队列中读取消息,对每条消息进行一些处理,并在处理完消息后,将响应消息发送到另一个本地MSMQ队列。

我正在使用异步循环从队列中读取消息,尽可能快地将它们出列并使用Task.Run调度它们进行处理以启动每条消息的处理(并且不要等待此Task.Run ..只是附加延续只会导致错误记录错误)。每个消息被同时处理,即在处理下一个消息之前不需要等待消息被完全处理。

在处理消息的最后,我使用MessageQueue的Send方法(不知何故异步但不是真的因为它必须等待磁盘写入才能返回-see System.Messaging - why MessageQueue does not offer an asynchronous version of Send)。

对于基准测试,我在队列中排队100K消息(100K消息的总大小约为100MB)然后我启动程序。在我的两台个人计算机上(一台SSD HD和另一台SATA2 HD,i7 CPU四核-8逻辑处理器)我在程序生命周期内达到~95%的CPU使用率(将100K消息出列,处理它们和发送回复)。消息尽可能快地出列,尽可能快地处理(此处涉及CPU),然后对发送到不同本地队列的每条消息进行响应。

现在在一台运行非HT双核CPU的虚拟机上(不知道什么是底层磁盘,但看起来远不如地雷......在基准测试期间,使用Perfmon我可以看到avg disk sec / write arround 10-15这个虚拟机上有ms,而我个人机器上的时间是2毫秒)当我运行相同的工作台时,我只能达到~55%的CPU(当我在机器上运行相同的工作台而没有向队列发送响应消息时我到达〜 90%CPU)。

我真的不明白这里有什么问题。似乎很清楚,向队列发送消息是问题并且减慢了程序的全局处理(以及要处理的消息的队列化),但为什么会考虑我使用Task.Run来启动每个出列消息的处理并最终响应发送,我不希望CPU未被充分利用。除非当一个线程正在发送消息时它阻止其他线程在等待返回(磁盘写入)时在同一个核心上运行,在这种情况下,考虑到延迟远远高于我的个人计算机,它可能是有意义的,但是一个线程等待I / O不应该阻止其他线程运行。

我真的想了解为什么我没有在这台机器上达到至少95%的CPU使用率。我盲目地说这是由于较差的磁盘I / O性能,但我仍然不明白为什么它会导致CPU利用不足,因为我正在使用Task.Run同时运行处理。它也可能是一些与磁盘完全无关的系统问题,但考虑到MessageQueue.Send似乎是问题,并且此方法最终将消息写入内存映射文件+磁盘,我看不出性能问题可能来自何处除了磁盘。

当然,由于程序最大限度地提高了我自己计算机上的CPU使用率,因此确实存在系统性能问题,但我需要找到VM系统上的瓶颈,以及为什么它会影响并发性/速度。我的申请。

有什么想法吗?

2 个答案:

答案 0 :(得分:3)

要检查较差的光盘和/或CPU利用率,只有一个工具:Windows Performance Toolkit。有关如何使用它的示例,请参阅here。 您应该从Windows 8.1 SDK(需要.NET 4.5.1)中获取最新版本,它提供了大多数功能,但Windows 8 SDK中的功能也很好。

您可以获得图表%CPU利用率和%磁盘利用率。如果任何一个是100%而另一个是低,那么你就找到了瓶颈。由于它是一个系统范围的分析器,您可以检查msmq服务是否正在使用光盘,或者您或其他人(例如病毒扫描程序是常见问题)。

您可以直接访问调用堆栈并检查哪个进程和线程确实唤醒了应该全速运行的工作线程。然后你可以跳转到准备线程并处理并检查它在准备你的线程之前做了什么。这样你就可以直接验证阻碍它的东西了。

不再猜测。你真的可以看到系统正在做什么。

要在CPU Usage Precise视图中进一步分析以下列:

  • NewProcess
  • NewThreadId
  • NewThreadStack(框架标签)
  • ReadyingProcess
  • ReadyingThreadId
  • 准备好(我们)总和
  • 等待(我们)总和
  • 等待(我们)
  • %CPU使用率

然后在你的进程中向下钻取一个调用堆栈,看看应该在一个应该全速运行的线程中发生高等待(us)时间。你可以深入到一个单独的事件,直到你不能去进一步。然后,您将在Readying Process和ReadyingThreadId中看到值。转到该进程/线程(它可以是您自己的)并重复该过程,直到您最终进行某些阻塞操作,该操作涉及磁盘IO或休眠或长时间运行的设备驱动程序调用(例如病毒扫描程序或vm驱动程序)。

答案 1 :(得分:0)

如果磁盘I / O性能计数器看起来不是很高,我会在虚拟机管理程序级别看下一步。假设您运行完全相同的代码,使用VM会为整个堆栈(CPU,RAM,磁盘)增加延迟。您也许可以在虚拟机管理程序级别调整CPU调度,看看这是否会增加CPU利用率。

我还考虑临时使用RAMDisk进行性能测试。这将消除磁盘/ SAN延迟,您可以看看是否能解决您的问题。