确定在时间关键型循环中冻结 200 毫秒的原因

Idendify the reason for a 200 ms freezing in a time critical loop

本文关键字:冻结 时间 循环 键型      更新时间:2023-10-16

问题的新描述:

我目前在测试环境中运行我们的新数据采集软件。该软件有两个主要线程。一个包含一个快速循环,该循环与硬件通信并将数据推送到双缓冲区中。每隔几秒钟,此循环冻结 200 毫秒。我做了几次测试,但没有一个让我弄清楚软件在等待什么。由于该软件相当复杂,并且测试环境也可能干扰软件,因此我需要一种工具/技术来测试记录器线程在被阻塞 200 毫秒时正在等待的内容。什么工具对实现这一目标有用?

原始问题:

在我们的数据采集软件中,我们有两个线程提供主要功能。一个线程负责从不同的传感器收集数据,第二个线程将数据保存到大块中的光盘中。数据收集在双缓冲区中。它通常每个项目包含 100000 字节,每秒最多收集 300 个项目。一个缓冲区用于在数据收集线程中写入,一个缓冲区用于读取数据并将其保存到第二个线程中的磁盘。如果已读取所有数据,则切换缓冲区。缓冲区的切换似乎是一个主要的性能问题。每次缓冲区切换时,数据收集线程都会阻塞大约 200 毫秒,这太长了。但是,偶尔会发生切换速度更快,几乎不需要任何时间。(测试电脑:Windows 7 64位,i5-4570 CPU @3.2 GHz(4核),16 GB DDR3(800 MHz))。

我的猜测是,性能问题与内核之间交换的数据有关。只有当线程偶然在同一内核上运行时,交换才会快得多。我考虑过设置线程亲和掩码以强制两个线程在同一内核上运行,但这也意味着,我失去了真正的并行性。另一个想法是让缓冲区在切换之前收集更多数据,但这大大降低了数据显示的更新频率,因为它必须等待缓冲区切换才能访问新数据。

我的问题是:是否有一种技术可以将数据从一个线程移动到另一个线程,而不会干扰收集线程?

编辑:双缓冲区实现为两个用作环形缓冲区的 std::vector。布尔 (int) 变量用于告知哪个缓冲区是活动的写入缓冲区。每次访问双精度缓冲区时,都会检查 bool 值以了解应使用哪个向量。切换双缓冲区中的缓冲区只是意味着切换此布尔值。当然,在切换过程中,所有读取和写入都会被互斥锁阻止。我不认为这个互斥锁可能会阻塞 200 毫秒。顺便说一下,对于每个开关事件,200 ms的重现性非常高。

锁定和释放互斥锁只是为了切换一个布尔变量不会花费 200 毫秒。

主要问题可能是两个线程以某种方式相互阻塞。这种阻塞称为锁争用。基本上,每当一个进程或线程尝试获取另一个进程或线程持有的锁时,就会发生这种情况。相反,并行性是两个线程相互等待完成其部分工作,其效果与单线程方法相似。

为了进一步阅读,我推荐阅读这篇文章,它更详细地描述了锁争用。

由于您在Windows上运行,也许您使用Visual Studio? 如果是,我会求助于VS分析器,在这种情况下非常好(恕我直言),一旦您不需要检查数据/指令缓存(那么英特尔的vTune是一个自然的选择)。根据我的经验,VS 足以捕获争用问题以及 CPU 瓶颈。您可以直接从VS运行它或作为独立工具运行它。您不需要在测试计算机上安装 VS,只需复制该工具并在本地运行即可。

VSPerfCmd.exe/start:SAMPLE/attach:12345/output:sample - 附加到进程 12345 并收集 CPU 采样信息
VSPerfCmd.exe/detach:12345 - 从进程中
分离VSPerfCmd.exe/shutdown - 关闭探查器,写入 samples.vsp(请参阅第一行)

然后,您可以打开该文件并在Visual Studio中检查它。 如果您没有看到任何使 CPU 忙碌的内容切换到争用分析 - 只需将"start"参数从"SAMPLE"更改为"并发"

该工具位于%YourVSInstallDir%\Team Tools\性能工具\下,AFAIR可从VS2010
获得祝你好运

在聊天中讨论了这个问题之后,事实证明Windows性能分析器是一个合适的工具。该软件是 Windows SDK 的一部分,可以在命令窗口中使用命令 wprui 打开。(Alois Kraus发布了这个有用的链接:http://geekswithblogs.net/akraus1/archive/2014/04/30/156156.aspx 在聊天中)。以下步骤揭示了软件一直在等待的内容:

  • 使用默认设置使用 WPR 记录信息,并将保存的文件加载到 WPA 中。
  • 确定相关线程。在这种情况下,记录线程和保存线程显然具有最高的 CPU 负载。可以轻松识别保存线程。由于它将数据保存到光盘,因此它是具有文件访问权限的光盘。(查看内存>硬故障)
  • 查看计算>CPU 使用率(精确),然后选择按进程、线程划分的利用率。选择要分析的过程。最好按顺序显示列:NewProcess、ReadyingProcess、ReadyingThreadId、NewThreadID、[黄色条]、就绪 (μs) 总和、等待 (μs) 总和、计数...
  • 在"准备过程"下,我查找了等待时间(μs)最大的进程,因为我希望这个进程是造成延迟的原因。
  • 在 ReadyingThreadID 下,我检查了引用 NewThreadId 列中延迟的线程的每一行。经过短暂的搜索,我发现了一个线程,该线程显示大约 100 毫秒的频繁等待,它总是显示为一对。在 ReadyingThreadID 列中,我能够读取记录循环正在等待的线程的 ID。
  • 根据其 CPU 使用率,该线程基本上什么都不做。在我们的特殊情况下,这让我假设串行端口io命令可能会导致这种等待。停用它们后,延迟消失了。重要的发现是,200 ms延迟实际上由两个100 ms延迟组成。

进一步的分析表明,通过虚拟串行端口对获取数据命令有时会丢失。这可能与数据保存和压缩循环中非常高的 CPU 负载有关。如果 fetch 命令丢失,则不会收到任何数据,并且第一次和第二次尝试接收数据时超时,超时时间为 100 毫秒。