4

我有一个用 C++ 编写的大型应用程序的性能问题。该程序仅使用 150% 的 CPU,而服务器是 24 核超线程 EPYC 和其他类似应用程序可以可靠地达到预期的 4800% CPU 负载。iotop显示几乎没有 I/O,这是预期的。

由于该程序显然既不受 I/O 限制也不受 CPU 限制,我检查strace并发现绝大多数跟踪调用都是等待单个futex. 也就是说:程序的 50 个线程中有 48 个似乎锁定了同一个 futex,这很好地解释了为什么 CPU 负载仅勉强超过 100%。

例子:

[pid 11581] futex(0x55acec47a900, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 11580] futex(0x55acec47a900, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 11579] futex(0x55acec47a900, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 11578] futex(0x55acec47a900, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 11577] futex(0x55acec47a900, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 11576] futex(0x55acec47a900, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>

现在我的问题是:我如何找到有问题的代码?该程序不是死锁,只是速度慢,所以通常的查找死锁的技术不起作用。

4

2 回答 2

6

我发现自己最好的方法是在 GDB 中运行该程序。由于大多数线程都被阻塞,info threads将显示大多数线程处于相同状态。对我来说,这恰好被阻止在__lll_lock_wait. 切换到这些线程中的任何一个都会给我一个堆栈跟踪,显示我最终是如何进入__lll_lock_wait. 在堆栈的三层上,我发现了我的违规代码。

于 2020-07-14T15:48:01.397 回答
0

如何找到锁定 Linux futex 的 C++ 行?

如果您接受更改您的 C++ 源代码,您可以使用最近的GCC编译器和使用 Ian Taylor 的libbacktraceg++ -O -g来编译它(因此使用DWARF调试信息)。该库在运行时使用ELF可执行文件共享库中的 DWARF 调试信息提供了很好的回溯信息。

然后,您可以子类化锁定的 C++ 类(例如std::mutexstd::lock_guard)或添加额外的 C++ 代码(可能使用预处理器 X-macros)来使用该回溯库。

还可以考虑使用GNU gprof的分析方法。

另一种可能的方法(仅适用于超过十万行 C++ 的大型代码库)可能是使用动态链接器技巧(例如LD_PRELOAD,参见ld.so(8))重新定义您的 C++ 标准库,或者编写您的GCC 插件,用于修改与futex(7)锁定 C++ 类相关的发出代码。

对于小型代码库,还可以考虑编写您的专用程序(本着Qt moc的精神)来转换您的 C++ 代码(例如,自动将 C++ 调用添加到libbacktrace函数中),然后更新您的构建自动化(例如您的Makefile)以使用它。

对于一个具体的例子,查看 GCC 的源代码

但是请注意,死锁或与同步相关的错误是典型的heisenbugs

所以预算几周的调试工作。

于 2020-07-15T04:29:48.290 回答