_L_unlock_16的性能瓶颈

时间:2013-07-08 10:43:49

标签: c++ multithreading pthreads gperftools

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

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

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

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

我想了解为什么我的程序花了很多时间在_L_unlock_16以及我可以做些什么来缓解它。

1 个答案:

答案 0 :(得分:2)

然而, _L_unlock_16 不再是您的代码的功能。你有没有看过上面这个功能?程序等待时,它的调用者是什么?你说这个程序浪费了50%在里面等待。但是,该程序的哪一部分命令该操作?它是否来自内存alloc / dealloc ops?

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

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

编辑#1:

呃..当我说" stacktrace"我的意思是stacktrace,而不是callgraph。 Callgraph在琐碎的情况下可能看起来不错,但是在更复杂的情况下,它会被破坏并且不可读,并且会将珍贵的细节隐藏起来并压缩成#34;但是,幸运的是,这里的情况看起来很简单。

请注意开头:"处理字,99x"。我认为" 99x"是通话计数。然后,查看" tagger-parse":97x。从那以后:

  • 61x进入rebuildFeatures,其中41x直接进入解锁,20(13)间接进入解锁
  • 23x进入buildLattice,其中21x进入解锁

猜测这是CRF ++非常严重地使用锁定。对我来说,似乎只是观察CRF内部锁定的影响。它当然不是内部锁定的。

似乎每个" processWord"至少锁定一次。很难说没有看代码(是开源吗?我没有检查过......),从堆栈跟踪中它会更明显,但如果它确实锁定了一次" processWord&#34 ;它甚至可能是一种全球性的锁定"保护"一切"来自"所有线程"并导致所有作业序列化。随你。无论如何,显然,它是CRF ++的内部锁定和等待。

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

如果共享CRF对象,请取消共享并查看上述内容。

但是,如果他们在幕后分享,那么可行性很小。将您的库更改为具有更好线程支持的库,或修复库,或忽略并将其与当前性能一起使用。

最后的建议可能听起来很奇怪(它运作缓慢,对吧?为什么要忽略它?),但实际上是最重要的一个,你应该先尝试一下。如果并行任务具有类似的"数据配置文件",则很可能他们将尝试在相同的大致时刻敲击相同的锁。想象一个中等大小的缓存,它保存按其第一个字母排序的单词。在那里,有26个条目的阵列。每个条目都有一个锁和一个单词列表。如果你运行100个线程,每个线程将首先检查" mom"然后"爸爸"然后"儿子" - 然后所有100个线程将首先命中并在#34; M"然后在" D"然后在" S"。嗯,大约/可能当然。但是你明白了。如果数据配置文件更随机,那么它们相互之间的阻塞要少得多。请注意,处理一个单词是一个小任务,并且您尝试处理相同的单词。即使内部CRF的锁定很聪明,它也必然会遇到相同的区域。再次尝试使用更加分散的数据。

除此之外,线程费用这一事实。如果使用锁防范某些比赛,那么每次锁定/解锁都会花费,因为至少他们必须停下来并检查锁是否打开" (抱歉非常不精确的措辞)。如果数据到进程相对于锁定量很小,那么添加更多线程将无济于事,只会浪费时间。对于检查一个单词,甚至可能发生单个锁的单独处理比处理单词需要更长的时间!但是,如果要处理的数据量较大,那么与处理数据相比,翻转锁的成本可能会开始变得可以忽略不计。

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

如果您注意到分成4个线程(每秒2500w)的10k字的速度比一个线程快40%-30%甚至25% - 在这里你就去了!你只是给它一个太小的工作。它是针对较大的那些量身定制和优化的!

但是,另一方面,可能会发生分裂超过4个线程的10k字不能更快地工作,或者更糟糕的是,工作得更慢 - 然后它可能表明该库处理多线程非常错误。现在尝试其他的东西,比如从它上剥离线程或修复它。