2013-07-08 106 views
0

我正在嘗試使用谷歌perf工具CPU分析器來調試多線程程序的性能問題。單線程需要250毫秒,而4線程需要900毫秒左右。性能瓶頸_L_unlock_16

我的程序有一個跨線程共享的mmap'ed文件,所有操作都是隻讀的。此外,我的程序創建了大量不通過線程共享的對象。 (具體來說,我的程序使用CRF ++庫來做一些查詢)。我想弄清楚如何讓我的程序在多線程下表現更好。由gperf工具的CPU分析器生成的調用圖顯示我的程序在_L_unlock_16中花費了大量時間(大約50%)。

在網絡中搜索_L_unlock_16,指出一些錯誤報告,並提供與libpthread關聯的規範提示。但除此之外,我無法找到任何有用的信息進行調試。

簡要描述我的程序的功能。我在文件中有幾句話(4)。在我的程序中,我有一個processWord(),它使用CRF ++處理單個單詞。這個processWord()是每個線程執行的內容。我的main()從文件中讀取單詞,每個線程並行運行processWord()。如果我處理一個單詞(因此只有一個線程)需要250毫秒,所以如果我處理所有4個單詞(因此4個線程),我預計它完成了250毫秒的時間,但正如我上面提到的,它需要大約900毫秒。 這是執行的調用圖 - https://www.dropbox.com/s/o1mkh477i7e9s4m/cgout_n2.png

我想知道爲什麼我的計劃是在_L_unlock_16花費大量的時間和我能做些什麼,以減輕它。

回答

2

再次,_L_unlock_16不是你的代碼的函數。你有沒有看過上面的那功能?當程序等待時,它的調用者是什麼?你說過這個程序浪費了50%的內部空間。但是,該程序的哪一部分命令該操作?它是從內存分配/ dealloc操作?

該函數似乎來自libpthread。 CRF +是否以任何方式處理線程/ libpthread?如果是,那麼圖書館可能配置不當?或者,它可能通過在所有地方添加鎖來實現一些「基本的線程安全性」,並且不能很好地構建多線程?這些文檔對此有何評論?

就我個人而言,我猜它忽略了線程,並且您已經添加了所有線程。我可能是錯的,但如果這是真的,那麼CRF ++可能根本就不會調用這個'unlock'函數,並且'unlock'在你的代碼中調用了線程/鎖/隊列/消息等。多次停止該程序,並查看誰稱爲解鎖。如果它真的花費50%坐在解鎖,你很快就會知道是誰使用鎖,你將能夠消除它或至少進行更精細的研究。

編輯#1:

呃..當我說「stacktrace」時,我的意思是stacktrace,而不是callgraph。在微不足道的情況下,Callgraph可能看起來不錯,但在更復雜的情況下,它可能會被破壞並且無法讀取,並會將珍貴的細節隱藏成「緊湊」的形式。但是,幸運的是,這裏的情況看起來很簡單。

請注意開頭部分:「處理字,99x」。我假設「99x」是通話計數。然後,看看「tagger-parse」:97x。從該:

  • 61X到rebuildFeatures從中41x的推移直接進入解鎖和20(13)間接地把它
  • 23X進入往復其中21X進入解鎖

我最好buildLattice猜測它是CRF ++使用非常大的鎖定。對我而言,你似乎只是觀察CRF內部鎖定的影響。它當然不是內部無鎖的。

它似乎每個「processWord」鎖定至少一次。如果沒有查看代碼(它是開源的嗎?我沒有檢查過)很難說,從堆棧跟蹤中它會更加明顯,但是如果它真的鎖定一次「processWord」,它甚至可能是一種「全局鎖定「,它保護」所有線程「中的」一切「,並導致所有作業序列化。隨你。無論如何,顯然,這是CRF ++的內部鎖定和等待。

如果你的CRF對象是真的(真的)不共享跨線程,然後從CRF刪除線程配置標誌,祈禱他們足夠理智,不使用任何靜態變量,也不全局對象,添加一些自己的鎖定(如果需要)處於最高職位/成果級別並重試。你現在應該快得多。

如果共享CRF對象,請取消共享它們並參閱上文。

但是,如果他們在幕後分享,那就沒什麼可行的了。將您的庫更改爲具有更好線程支持的庫,或修復庫,或忽略並使用當前性能。

最後的建議可能聽起來很奇怪(它工作的很慢,對吧?爲什麼要忽略它?),但實際上是最重要的一個,你應該先嚐試一下。如果並行任務具有相似的「數據配置文件」,那麼他們很可能會嘗試在相同的大致時間點擊相同的鎖。設想一箇中等大小的緩存,保存按第一個字母排序的單詞。在頂層,有26個條目。每個條目都有一個鎖和一個單詞列表。如果你運行100個線程,每個線程首先檢查「媽媽」,然後「爸爸」,然後「兒子」 - 那麼所有100個線程將首先擊中並在「M」等待對方,然後在「D」然後在「S 」。那麼,大概/當然可能。但你明白了。如果數據配置文件更隨機,那麼它們會相互阻塞得更少。注意處理一個單詞是一個小任務,並且您嘗試處理相同的單詞。即使內部通用報告格式的鎖定很智能,但它肯定會碰到相同的區域。再次嘗試使用更分散的數據。

除此之外,線程成本。如果使用鎖來防範賽事,那麼每次鎖定/解鎖費用至少是因爲他們不得不「停止並檢查鎖是否打開」(對不起非常不精確的措辭)。如果數據到進程相對於lockcheck數量很小,那麼添加更多的線程將無濟於事,而且會浪費時間。爲了檢查一個單詞,甚至可能發生單個處理單個鎖需要比處理單詞更長的時間!但是,如果要處理的數據量較大,那麼與處理數據相比,翻轉鎖的成本可能會開始變得微不足道。

準備一組100個或更多的單詞。在一個線程上運行並測量它。然後隨機分割這些單詞並在2和4個線程上運行它。和措施。如果不是更好,請試試1000和10000字。越多越好,當然,請記住,測試不應該持續到您的下一個生日;)

如果您發現10k字分裂爲4線程(每秒2500w)約40%-30%甚至比一條線上的速度快25% - 在這裏,你去!你只是給它一個太小的工作。它是爲更大的設計和優化的!但是,另一方面,可能發生的情況是,分割成4個線程的10k個單詞不能工作得更快,或者更糟糕,工作速度更慢 - 那麼它可能表明該庫處理多線程錯誤。現在嘗試其他的東西,如從其中刪除線程或修復它。

+0

我修改了原始描述,更詳細地介紹了我的程序和調用圖。此外,即使在CRF ++上,我的程序中也沒有任何特定的鎖,我正在使用CRF ++的修改版本,該版本是線程安全的並且可重入,而不使用任何鎖。 – Jithin

+0

看我的編輯。沒有看到你和CRF的確切代碼,很難再說什麼。我沒有時間深入挖掘它,對不起。如果上述提示不夠,也許有人會出現誰知道這個圖書館的確切問題和解決方案。 – quetzalcoatl

+0

感謝您的建議。但我還沒有能夠解決這個問題。在檢查strace -c時,它顯示futex系統調用佔用了90%的用戶CPU時間。此外,cachegrind顯示大量數據緩存未命中。我現在正在朝這個方向調試。 – Jithin