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

标签 c# performance async-await msmq

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

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

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

在消息处理结束时,我正在使用 MessageQueue 的 Send 方法(不知何故异步但不是真的,因为它必须在返回之前等待磁盘写入 - 请参阅 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 我可以看到平均磁盘秒/写大约 10-15 ms 在这个 VM 上,而在我的个人机器上大约是 2ms)当我运行同一个工作台时,我只达到 ~55% CPU(当我在机器上运行同一个工作台而不向队列发送响应消息时,我达到了 ~ 90% CPU)。

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

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

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

有什么想法吗?

最佳答案

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

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

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

不用再猜了。您可以真正看到系统在做什么。

要在 CPU Usage Precise 中进一步分析,请查看以下列:

  • 新工艺
  • 新线程Id
  • NewThreadStack(框架标签)
  • 准备过程
  • ReadyingThreadId
  • 准备好(我们)金额
  • 等待(我们)总和
  • 等(我们)
  • %CPU 使用率

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

关于c# - CPU 未充分利用。由于阻塞 I/O?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19748129/

相关文章:

c# - 内存流不可扩展

c# - 为什么派生类的泛型会产生非派生类?

c# - 如何连接 2 个字节?

c# - 如何使 HttpContext 在同步调用的任务中可用?

c# - 异步方法是否有可能在 C# 中返回 null?

c# - 如何将项目添加到 Windows Shell(右键单击)?

python - 了解 numpy 内存映射的性能

javascript - 在 JS 中显式指定 return 语句与根本没有 return 语句有什么区别?

python - 评估函数不同参数的性能

json - 东京和塞尔德 : deserializing JSON