【问题标题】:CPU underutilized. Due to blocking I/O?CPU 未充分利用。由于阻塞 I/O?
【发布时间】:2013-11-13 21:57:46
【问题描述】:

我试图找出 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 逻辑 proc-)在程序生命周期期间,CPU 使用率达到约 95%(将 100K 消息出列,处理它们并发送响应)。消息尽可能快地出列,尽可能快地处理(此处涉及 CPU),然后响应发送到不同本地队列的每条消息。

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

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

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

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

有什么想法吗?

【问题讨论】:

  • 因为您的一个或多个磁盘卷的利用率可能接近 100%。
  • Task.Run() 使用具有最大线程数的应用程序全局线程池。我猜想随着您的处理负载,所有这些线程都可能被阻塞。要检查发生这种情况是因为您的磁盘不够快还是因为您没有足够的线程,可能会提高最大线程数并再次测量。但就像 RBarryYoung 一样,我敢打赌 IO 会被刷爆。
  • @RBarryYoung :看起来不像。在那台机器上总执行大约需要 60 秒,所以 100MB 在 60 秒内,我不认为磁盘达到 100% 的利用率(实际上使用 perfmon 我可以看到磁盘大部分时间都是空闲的)。
  • @confusopoly:嗯,有趣的是,当我查看性能计数器时,该应用程序在该机器上运行 22 个逻辑线程。无论如何,我认为在 .NET 4.5 中,线程池中的最大线程数默认设置为一个非常高的值,并且无论如何不可能达到该最大值。您是否有一些关于您声明的文档的链接(线程池中默认的最大线程数较低)?
  • 很容易测试@confusopoly 的理论(我之前遇到过):ThreadPool.SetMinThreads。默认线程数没有那么高。

标签: c# performance async-await msmq


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

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

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

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

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

    • 新进程
    • NewThreadId
    • NewThreadStack(帧标签)
    • 准备过程
    • ReadingThreadId
    • 准备好(我们)总和
    • 等待(我们)总和
    • 等待(我们)
    • %CPU 使用率

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

    【讨论】:

    • 谢谢阿洛伊斯。昨天已经在机器上安装了它并试了一下。磁盘利用率没有表现出任何疯狂,磁盘似乎大部分时间都处于空闲状态。该工具仍然是一个很棒的工具,我很惊讶,并且花了几个小时在它上面,但是学习曲线非常陡峭(至少对我来说),我真的不知道在哪里可以找到瓶颈。这不像程序说的那样:“这是你的瓶颈”,你有这么多 ETW 计数器,我真的不知道在哪里看。而且不知道我正在读取的值是否可以接受,或者它是否表明存在瓶颈。
    • 您是否可以访问运行 VM 的真机?也可能是虚拟机确实页面疯狂,导致您的应用程序变慢,而您在托管操作系统中可能看不到。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-24
    • 1970-01-01
    • 2018-04-06
    • 1970-01-01
    • 2021-05-24
    相关资源
    最近更新 更多