0

我正在尝试使用 google perf tools CPU profiler 来调试多线程程序的性能问题。单线程需要 250 毫秒,而 4 线程需要大约 900 毫秒。

我的程序有一个跨线程共享的 mmap 文件,所有操作都是只读的。此外,我的程序创建了大量不跨线程共享的对象。(具体来说,我的程序使用 CRF++ 库进行一些查询)。我试图弄清楚如何让我的程序在多线程中表现得更好。gperf 工具的 CPU 分析器生成的调用图显示我的程序在_L_unlock_16中花费了大量时间(大约 50%)。

在网络上搜索 _L_unlock_16 指向一些错误报告,规范表明它与 libpthread 相关联。但除此之外,我找不到任何有用的调试信息。

简要说明我的程序的功能。我在文件中没有几个字(4)。在我的程序中,我有一个使用 CRF++ 处理单个单词的 processWord()。这个 processWord() 是每个线程执行的。我的 main() 从文件中读取单词,每个线程并行运行 processWord()。如果我处理一个单词(因此只有 1 个线程)需要 250 毫秒,所以如果我处理所有 4 个单词(因此是 4 个线程),我希望它在 250 毫秒的同时完成,但是正如我上面提到的,它需要大约 900 毫秒。这是执行的调用图 - https://www.dropbox.com/s/o1mkh477i7e9s4m/cgout_n2.png

我想了解为什么我的程序在 _L_unlock_16 上花费大量时间,以及我可以做些什么来缓解它。

4

1 回答 1

2

_L_unlock_16再次不是您的代码的功能。你看过那个函数上面的stracktraces吗?程序等待时它的调用者是什么?您说过该程序浪费了 50% 的等待时间。但是,程序的哪一部分命令了该操作?又是来自内存分配/释放操作吗?

该函数似乎来自 libpthread。CRF+ 是否以任何方式处理线程/libpthread?如果是,那么库可能配置错误?或者它可能通过在任何地方添加锁来实现一些“基本线程安全”,并且根本不适合多线程?文档对此有何评论?

就个人而言,我猜它会忽略线程并且您已经添加了所有线程。我可能是错的,但如果这是真的,那么 CRF++ 可能根本不会调用那个“解锁”函数,而“解锁”是从你的协调线程/锁/队列/消息等的代码中调用的?暂停程序几次,看看是谁调用了解锁。如果它真的花费 50% 的时间在解锁上,你会很快知道是谁导致了这个锁被使用,你将能够消除它,或者至少进行更精细的研究。

编辑#1:

嗯..当我说“堆栈跟踪”时,我的意思是堆栈跟踪,而不是调用图。Callgraph 在微不足道的情况下可能看起来不错,但在更复杂的情况下,它会被破坏和不可读,并且会将宝贵的细节隐藏为“压缩”形式。但是,幸运的是,这里的情况看起来很简单。

请注意开头:“Process word, 99x”。我假设“99x”是通话次数。然后,查看“tagger-parse”:97x。从此:

  • 61x 进入重建功能,其中 41x 直接进入解锁,20(13) 间接进入
  • 23x 用于 buildLattice,21x 用于解锁

这是 CRF++ 大量使用锁定。对我来说,您似乎只是观察了 CRF 内部锁定的效果。它当然不是内部无锁的。

每个“processWord”似乎至少锁定一次。不看代码很难说(它是开源的吗?我还没有检查过..),从堆栈跟踪来看会更明显,但如果它真的每个“processWord”锁定一次,它甚至可能是一种“全局锁”,它保护“一切”不受“所有线程”的影响,并导致所有作业序列化。任何。无论如何,很明显,锁定和等待的是 CRF++ 的内部结构。

如果你的 CRF 对象真的(真的)没有跨线程共享,那么从 CRF 中删除线程配置标志,祈祷它们足够理智,不使用任何静态变量或全局对象,在最顶层的工作中添加一些自己的锁定(如果需要) /result 级别并重试。你现在应该更快地拥有它。

如果 CRF 对象是共享的,请取消共享它们并参见上文。

但是,如果它们是在幕后共享的,那么就没有什么可行的了。将您的库更改为具有更好线程支持的库,或者修复库,或者忽略并以当前性能使用它。

最后一个建议可能听起来很奇怪(它工作很慢,对吧?那为什么要忽略它?),但实际上是最重要的,你应该先尝试一下。如果并行任务具有相似的“数据配置文件”,那么它们很可能会在相同的近似时间尝试命中相同的锁。想象一个中等大小的缓存,它保存按首字母排序的单词。在顶层有一个数组,比如说,26 个条目。每个条目都有一个锁和里面的单词列表。如果您运行 100 个线程,每个线程将首先检查“妈妈”,然后是“爸爸”,然后是“儿子”——那么所有这 100 个线程将首先在“M”处相互等待,然后在“D”处,然后在“S”处”。好吧,大约/可能当然。但你明白了。如果数据配置文件更随机,那么他们' d 互相阻挡少得多。请注意,处理一个单词是一项.. 小任务,并且您尝试处理相同的单词。即使内部 CRF 的锁定是智能的,它也必然会命中相同的区域。使用更分散的数据再试一次。

再加上线程成本的事实。如果使用锁来防止竞争,那么每次锁定/解锁都会产生成本,因为至少他们必须“停止并检查锁是否打开”(对不起,措辞非常不准确)。如果要处理的数据相对于锁检查的数量很小,那么添加更多线程将无济于事,只会浪费时间。对于检查一个单词,甚至可能发生单独处理单个锁的时间比处理单词要长!但是,如果要处理的数据量更大,那么与处理数据相比,翻转锁的成本可能开始可以忽略不计。

准备一组 100 个或更多的单词。在一个线程上运行并测量它。然后随机划分单词并在 2 和 4 个线程上运行。并测量。如果不是更好,请尝试 1000 和 10000 字。当然,越多越好,请记住测试不应持续到您的下一个生日;)

如果您注意到 10k 个单词分成 4 个线程(每天 2500w)的工作效率比在一个线程上快 40%-30% 甚至 25% - 来吧!你只是给了它一个太小的工作。它是为更大的量身定制和优化的!

但是,另一方面,10k 字拆分为 4 个线程可能不会工作得更快,或者更糟,工作得更慢 - 这可能表明库处理多线程非常错误。现在尝试其他的事情,比如剥去螺纹或修复它。

于 2013-07-08T12:40:08.293 回答