0

マルチスレッド プログラムのパフォーマンスの問題をデバッグするために、Google パフォーマンス ツールの CPU プロファイラーを使用しようとしています。シングル スレッドでは 250 ミリ秒かかりますが、4 スレッドでは約 900 ミリ秒かかります。

私のプログラムには、スレッド間で共有される mmap されたファイルがあり、すべての操作は読み取り専用です。また、私のプログラムは、スレッド間で共有されない多数のオブジェクトを作成します。(具体的には、私のプログラムは CRF++ ライブラリを使用してクエリを実行します)。マルチスレッドでプログラムのパフォーマンスを向上させる方法を見つけようとしています。gperf ツールの CPU プロファイラーによって作成されたコール グラフは、私のプログラムが_L_unlock_16で多くの時間 (約 50%) を費やしていることを示しています。

Web で _L_unlock_16 を検索すると、libpthread に関連していることを示唆する正規のバグ レポートがいくつか指摘されました。しかし、それ以外に、デバッグに役立つ情報を見つけることができませんでした。

私のプログラムが何をするかの簡単な説明。ファイルに単語がほとんどありません (4)。私のプログラムには、CRF++ を使用して単一の単語を処理する processWord() があります。この processWord() は、各スレッドが実行するものです。私の main() はファイルから単語を読み取り、各スレッドは processWord() を並行して実行します。1 つの単語 (したがって 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はコードの関数ではありません。その関数の上の stracktrace を見たことがありますか? プログラムが待機しているときの呼び出し元は何ですか? あなたは、プログラムが内部で待機する 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」ごとに少なくとも1回はロックされるようです。コードを見ずに言うのは難しいです(オープンソースですか?私はチェックしていません..)、スタックトレースからはより明白になりますが、「processWord」ごとに1回ロックされる場合「すべて」を「すべてのスレッド」から保護し、すべてのジョブをシリアル化する「グローバル ロック」。なんでもいい。とにかく、明らかに、ロックして待機するのは CRF++ の内部です。

あなたの CRF オブジェクトが本当に (本当に) スレッド間で共有されていない場合は、CRF からスレッド構成フラグを削除し、静的変数やグローバル オブジェクトを使用しないように十分に正気であることを祈り、最上部のジョブに独自のロック (必要な場合) を追加します。 /結果レベルと再試行。これで、はるかに高速になるはずです。

CRF オブジェクトが共有されている場合は、共有を解除して上記を参照してください。

しかし、それらが舞台裏で共有されている場合、実行できることはほとんどありません。ライブラリをより優れたスレッド サポートを持つものに変更するか、ライブラリを修正するか、無視して現在のパフォーマンスで使用してください。

最後のアドバイスは奇妙に聞こえるかもしれません (動作が遅いですよね? なぜ無視する必要があるのでしょうか?) が、実際には最も重要なアドバイスであり、最初に試してみてください。並列タスクが同様の「データ プロファイル」を持っている場合、ほぼ同じ時間に同じロックをヒットしようとする可能性が非常に高くなります。最初の文字でソートされた単語を保持する中規模のキャッシュを想像してください。トップレベルには、たとえば 26 個のエントリの配列があります。各エントリにはロックと単語のリストが含まれています。最初に「お母さん」、「お父さん」、「息子」の順にチェックする 100 個のスレッドを実行すると、その 100 個のスレッドすべてが最初にヒットし、「M」、次に「D」、次に「S」で相互に待機します。 "。まあ、おおよそ/おそらくもちろんです。しかし、あなたはその考えを理解します。データ プロファイルがよりランダムである場合、d お互いをブロックすることがはるかに少なくなります。1 つの単語を処理するのは小さな作業であり、同じ単語を処理しようとすることに注意してください。内部 CRF のロックがスマートであっても、同じ領域にヒットすることは間違いありません。より分散したデータで再試行してください。

それに、スレッドのコストがかかるという事実を追加します。ロックを使用して何かが競合から保護されている場合、少なくとも「停止してロックが開いているかどうかを確認する」必要があるため、すべてのロック/ロック解除にコストがかかります(非常に不正確な表現で申し訳ありません)。処理するデータがロックチェックの量に比べて小さい場合、スレッドを追加しても効果がなく、時間を無駄にするだけです。1 つの単語をチェックするために、単語を処理するよりも 1 つのロックを処理するだけの方が時間がかかる場合もあります。ただし、処理するデータの量が多い場合、データの処理に比べてロックを反転するコストは無視できるようになる可能性があります。

100語以上のセットを用意してください。1 つのスレッドで実行して測定します。次に、単語をランダムに分割し、2 スレッドと 4 スレッドで実行します。そして測ります。うまくいかない場合は、1000 語と 10000 語で試してください。もちろん、テストは次の誕生日まで続くべきではないことを念頭に置いて、多ければ多いほど良いです;)

4 つのスレッドに分割された 10k ワード (1 秒あたり 2500w) が、1 つのスレッドよりも約 40% ~ 30%、さらには 25% 高速に動作することに気付いた場合は、どうぞ! あまりにも小さな仕事を与えただけです。それはより大きなもののために調整され、最適化されました!

しかし一方で、4 つのスレッドに分割された 10k ワードが高速に動作しないか、さらに悪いことに、動作が遅くなることがあります。その場合、ライブラリがマルチスレッドを非常に間違って処理していることを示している可能性があります。次に、スレッドを削除したり、修復したりするなど、他のことを試してください。

于 2013-07-08T12:40:08.293 に答える