C# CPU未充分利用。由于I/O阻塞?

C# CPU未充分利用。由于I/O阻塞?,c#,performance,async-await,msmq,C#,Performance,Async Await,Msmq,我试图找出C#server应用程序的瓶颈所在,该应用程序未充分利用CPU。我认为这可能是由于磁盘I/O性能差,与应用程序本身无关,但我很难从这个假设中得出一个事实 应用程序从本地MSMQ队列读取消息,对每条消息进行一些处理,处理完消息后,向另一个本地MSMQ队列发送响应消息 我正在使用异步循环从队列中读取消息,尽可能快地将它们出列,并使用Task分派它们进行处理。运行以启动对每条消息的处理(不要等待此任务。运行..只是将一个仅出现故障的延续附加到日志错误)。每个消息都是并行处理的,即在处理下一个

我试图找出C#server应用程序的瓶颈所在,该应用程序未充分利用CPU。我认为这可能是由于磁盘I/O性能差,与应用程序本身无关,但我很难从这个假设中得出一个事实

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

我正在使用异步循环从队列中读取消息,尽可能快地将它们出列,并使用Task分派它们进行处理。运行以启动对每条消息的处理(不要等待此任务。运行..只是将一个仅出现故障的延续附加到日志错误)。每个消息都是并行处理的,即在处理下一个消息之前,无需等待消息被完全处理

在消息处理结束时,我使用MessageQueue的Send方法(某种程度上是异步的,但不是真正的,因为它在返回之前必须等待磁盘写入-请参阅)

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

现在,在一个运行非HT双核CPU的虚拟机上(不知道底层磁盘是什么,但性能似乎远不如我的…在基准测试期间,使用Perfmon,我可以在这个虚拟机上看到平均磁盘秒/写入约10-15毫秒,而在我的个人机器上则约为2毫秒),当我运行同一台工作台时,我只能达到约55%的CPU(当我在机器上运行同一台工作台而不向队列发送响应消息时,我达到约90%的CPU)

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

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

这当然是一个系统性能问题,因为程序在我自己的计算机上最大限度地提高了CPU使用率,但我需要找出VM系统上的瓶颈到底是什么,以及为什么它会影响我的应用程序的并发性/速度


有什么想法吗?

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

在这里您可以看到%CPU利用率和%Disc利用率的图表。如果其中一个是100%,而另一个是低的,那么您就找到了瓶颈。由于它是一个系统范围的探查器,您可以检查msmq服务是否严重使用了该光盘,或者您或其他人(例如,病毒扫描程序是一个常见问题)

您可以直接访问调用堆栈,检查哪个进程和线程唤醒了您的工作线程,该线程应该全速运行。然后,您可以跳转到准备线程和进程,检查它在准备好线程之前做了什么。这样,您可以直接验证是什么阻碍了它这么长时间

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

要进一步分析,请在CPU使用率精确视图中启用以下列:

  • 新工艺
  • 新线程ID
  • 新线程堆栈(帧标记)
  • 读写过程
  • ReadyingThreadId
  • (美元)现款
  • 等(美)和
  • 等等(美国)
  • %CPU使用率

然后向下钻取进程中的调用堆栈,以查看应该全速运行的线程中出现高等待(us)时间的位置。您可以向下钻取一个事件,直到无法继续。然后您将在Reading process和Reading ThreadId中看到值。转到该进程/线程(它可以是您自己的)并重复该过程,直到您最终进入某种阻塞操作,该操作涉及磁盘IO或休眠或长时间运行的设备驱动程序调用(例如病毒扫描程序或vm驱动程序).

如果磁盘I/O性能计数器看起来没有异常高,我接下来会看看虚拟机监控程序级别。假设您运行的代码完全相同,使用虚拟机会给整个堆栈(CPU、RAM、磁盘)增加延迟。您可能可以在虚拟机监控程序级别调整CPU调度,看看这是否会提高CPU利用率

我也会考虑暂时使用RAMDISK进行性能测试。这将消除磁盘/SAN延迟,您可以看到