性能瓶颈在_L_unlock_16

Performance bottleneck at _L_unlock_16

本文关键字:unlock 性能瓶颈      更新时间:2023-10-16

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

我的程序有一个mmap'ed文件,它是跨线程共享的,所有操作都是只读的。此外,我的程序创建了大量的对象,这些对象不是跨线程共享的。(特别是我的程序使用crf++库来做一些查询)。我正试图弄清楚如何使我的程序与多线程性能更好。gperf工具的CPU分析器生成的调用图显示,我的程序在_L_unlock_16中花费了大量时间(大约50%)。。

在web上搜索_L_unlock_16指向一些错误报告,canonical暗示它与libpthread相关。但除此之外,我没有找到任何有用的调试信息。

程序功能的简要描述。我在一个文件(4)中有几个单词。在我的程序中,我有一个processWord(),它使用crf++处理单个单词。这个processWord()是每个线程执行的。我的main()从文件中读取单词,每个线程并行运行processWord()。如果我处理一个单词(因此只有一个线程),它需要250毫秒,所以如果我处理所有4个单词(因此4个线程),我预计它将在250毫秒内完成,然而,正如我上面提到的,它大约需要900毫秒。这是执行的代码- https://www.dropbox.com/s/o1mkh477i7e9s4m/cgout_n2.png

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

再次_L_unlock_16不是你代码的功能。您看过上面的stracktraces吗?这个函数呢?当程序等待时,它的调用者是什么?你说过这个项目浪费了50%的时间。但是,程序的哪一部分命令了这个操作?它是否再次从内存分配/dealloc操作?

这个函数似乎来自libpthread。CRF+是否以任何方式处理线程/libpthread ?如果是,那么可能库没有配置好?或者它通过在任何地方添加锁来实现一些"基本线程安全",并且根本不适合多线程?医生是怎么说的?

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

编辑# 1:

呃. .我说的"stacktrace"指的是stacktrace,不是callgraph。在简单的情况下,书法可能看起来不错,但在更复杂的情况下,它将是混乱的,不可读的,并将宝贵的细节隐藏在"压缩"的形式中。但是,幸运的是,这里的情况看起来很简单。

请注意开头:"Process word, 99x"。我假设"99x"是呼叫计数。然后,看看"tagger-parse":97x。来自:

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

crf++使用了大量的锁。在我看来,您似乎只是观察到CRF内部锁定的影响。它的内部肯定不是无锁的。

似乎每个"processWord"至少锁定一次。不看代码很难说(它是开源的吗?)我没有检查…),从堆栈跟踪它会更明显,但如果它真的锁定一次每个"processWord",它甚至可以是一种"全局锁",保护"一切"从"所有线程",并导致所有作业序列化。无论什么。无论如何,很明显,是crf++的内部机制锁定和等待。

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

如果CRF对象是共享的,取消共享,参见上面的步骤。

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

最后一个建议可能听起来很奇怪(它起作用很慢,对吧?所以为什么要忽略它呢?),但事实上它是最重要的,你应该先尝试一下。如果并行任务具有相似的"数据配置文件",那么它们很可能会尝试在相同的近似时刻命中相同的锁。想象一个中等大小的缓存,它保存按首字母排序的单词。在顶层有一个包含26个条目的数组。每个条目都有一个锁和一个单词列表。如果你运行100个线程,每个线程首先检查"妈妈",然后检查"爸爸",然后检查"儿子",那么所有这100个线程都将首先在"M","D"answers"S"处互相等待。嗯,大概/很可能。但你懂的。如果数据配置文件更加随机,那么它们之间的相互阻碍就会少得多。记住,处理一个单词是一个…小任务,你试着处理相同的单词。即使内部CRF的锁定是智能的,它也只能命中相同的区域。使用更分散的数据再试一次。

加上线程开销。如果某些东西是通过使用锁来防止竞争,那么每次锁/解锁都是有成本的,因为至少他们必须"停下来检查锁是否打开"(抱歉,措辞不准确)。如果要处理的数据相对于锁检查的数量来说很小,那么添加更多的线程将无济于事,而且只会浪费时间。对于检查一个单词,甚至可能发生单个锁的单独处理时间比处理单词的时间长!但是,如果要处理的数据量更大,那么与处理数据相比,翻转锁的成本可能开始可以忽略不计。

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

如果你注意到在4个线程上拆分10k个单词(每个线程2500w)的工作速度比在一个线程上快40%-30%-甚至25%,那就来吧!你给它的任务太小了。它是为更大的人量身定制和优化的!

但是,另一方面,可能发生在4个线程上拆分10k个单词并没有更快地工作,或者更糟,工作更慢——那么它可能表明库处理多线程非常错误。现在尝试其他的事情,比如从它剥离线程或修复它。