3

我正在尝试使用多线程 C++ 应用程序调试性能问题。基本上我的多线程程序(10 个线程)比单线程程序慢得多。

我一直在尝试 valgrind (callgrind)、gprof 和 gdb 等工具。但到目前为止,我无法弄清楚线程被阻塞的确切位置以及原因。gprof 和 callgrind 让我在每个函数上花费了全部时间。但是这个时间是否包括线程被阻塞的时间?是否有任何开源工具可用于调试此问题。

4

2 回答 2

1

即使我没有现成的答案,我也会从这里的评论切换,因为还有更多的空间来编写和格式化..

你能澄清一下“lahks”这个词吗?我在 Wiki 上只发现了一些松散相关的东西,但它纯粹是猜测,我不知道你的意思。

large number of objects per thread你说的一个。当你随机采样/停止时,你看过堆栈跟踪吗?我知道 alloc/dealloc 是堆栈跟踪中最常见的叶子,但是 * nonleaf *s 呢?您是否能够看到实际调用该 alloc/dealloc 的内容?这就是采样方法的重点——查看调用的原始数据,并从统计上估计哪些可能的起源是导致调用频率过高的原因。

由于大量优化或架构不匹配,您可能无法观察堆栈跟踪的“较高部分”(即,如果您的应用程序使用任务队列,那么大多数时候您只会看到“获取任务”,检查任务“,执行任务”步骤而不是真正的起源),但几乎在每个体系结构中您都可以进行充分调整(就任务排队而言 - 只需尝试对任务注册进行抽样!)

另一种方式 - alloc/dealloc 膨胀是非常普遍的:它通常与架构和算法有关,或者,好吧,错误。然而,这种事情不仅在“优化发布”构建中应该很容易观察到(在查看堆栈跟踪时存在问题),而且应该很快出现在“完整调试信息”构建中 - 整个系统的优化较少运行速度较慢,但​​您应该能够看到并收集所有可能来源的中间方法。

另一件事:您说过“多线程”的工作速度比“单线程”慢得多。这就产生了一个问题,你如何能够在它们之间切换?你有两个单独的实现吗?还是您只是在 1 个工作线程和 N 个工作线程之间调整线程池大小?用“alloc/dealloc”问题解决这个问题——也许你的每个线程每次都需要执行太多的设置/拆卸?

与单线程选项相比,尝试检查线程(作为一个组,也查看线程的生命周期)实际上必须重复准备的内容。

例如,单线程以某种方式节省了 alloc/dealloc 并且可能重用了某些结构),而 N 线程可能需要 N 次相同的结构。如果线程只是重复启动/停止而没有被重用,那么它们的 N*data 可能也没有被重用,因此 N 线程可能只是在实际工作之前的准备工作上浪费时间。

此外,如果您设法抓住了无关的分配方案 - 为什么不进一步追踪:停止后,退出分配器并尝试查看正在分配的内容。我的意思是,您可以检查正在写入该内存的内容,这可以让您进一步了解实际发生的情况。然而,这可能是一项非常费力的任务,特别是因为它必须重复很多次。我会把它作为最后的手段。

另一件事是——纯粹是猜测——你的平台可能在 alloc/dealloc 中有一些全局锁来“安全地跟踪”内存管理。这样,如果所有线程都按照自己的意愿管理自己的内存,那么线程将在每次内存分配/释放操作时相互等待。更改内存分配方案,或使用不同的内存管理器,或使用堆栈或 TLS,或将线程池拆分为单独的进程可能会有所帮助,因为它可以避免全局锁定的需要。但是,这只是一个非常遥远的猜测,没有一个解决方案是容易应用的。

对于这种笼统和模糊的谈话,我感到很抱歉。仅凭您提供的一些细节,很难说更多。我故意回避“可视化工作的工具”话题。如果您无法仅通过 sample/stop 方法看到正在发生的事情,那么所有可能的“线程可视化”工具很可能没有帮助:它们可能会向您显示与您现在看到的完全相同的内容,因为它们都分析相同的堆栈跟踪,只是比手动停止快一点..

于 2013-07-04T11:59:17.120 回答
1

一种可能性是您在单核 CPU 上运行多线程代码 :)

关于多线程的一个常见误解是,您可以通过将线程扔给问题来获得加速改进:这是错误的,除非您拥有真正的多核 CPU 和可并行化的问题(即可以拆分为可独立解决的子问题的问题)

也许您正在处理一个不可并行的问题(例如哈希计算)或使用 I/O 访问(这也是不可并行的)

于 2013-07-04T12:55:51.010 回答