20

第一に、この[種類の]質問はよく聞かれることを知っているので、私はできる限り読んだと言ってこれを前置きさせてください、そして私はまだ取引が何であるかを知りません。

大規模なアウターforループを並列化しました。ループの反復回数はさまざまで、通常は20〜150の間ですが、ループ本体は膨大な量の作業を行い、多くのローカル集中線形代数ルーチンを呼び出します(コードはソースの一部であり、外部の依存関係ではありません) 。ループ本体内には、これらのルーチンへの1000以上の呼び出しがありますが、それらはすべて互いに完全に独立しているため、並列処理の最有力候補になると思いました。ループコードはC++ですが、Cで記述された多くのサブルーチンを呼び出します。

コードは次のようになります。

<declare and initialize shared variables here>
#ifdef _OPENMP
#pragma omp parallel for                            \
  private(....)\
  shared(....)              \
  firstprivate(....) schedule(runtime)
#endif
  for(tst = 0; tst < ntest; tst++) {

     // Lots of functionality (science!)
     // Calls to other deep functions which manipulate private variables only
     // Call to function which has 1000 loop iterations doing matrix manipulation
     // With no exaggeration, there are probably millions 
     // of for-loop iterations in this body, in the various functions called. 
     // They also do lots of mallocing and freeing
     // Finally generated some calculated_values

     shared_array1[tst] = calculated_value1;
     shared_array2[tst] = calculated_value2;
     shared_array3[tst] = calculated_value3;

 } // end of parallel and for

// final tidy up

同期はまったくないはずです。スレッドが共有変数にアクセスするのは、だけであり、スレッドは、shared_arraysによってインデックス付けされた、それらの配列内の一意のポイントにアクセスしますtst

(マルチコアクラスターで!)スレッドの数を増やすと、表示される速度(このループを5回呼び出す場合)は次のようになります。

              Elapsed time   System time
 Serial:        188.149          1.031
 2 thrds:       148.542          6.788
 4 thrds:       309.586        424.037       # SAY WHAT?
 8 thrds:       230.290        568.166  
16 thrds:       219.133        799.780 

注目に値するのは、2スレッドから4スレッドの間のシステム時間の大幅なジャンプであり、経過時間は2スレッドから4スレッドに移動すると2倍になり、その後ゆっくりと減少します。

OMP_SCHEDULEさまざまなパラメータを試してみましたが、うまくいきませんでした。これは、各スレッドがmalloc/newとfree/deleteを頻繁に使用しているという事実に関連していますか?これは一貫して8GBのメモリで実行されていますが、それは問題ではないと思います。率直に言って、システム時間の大幅な増加により、スレッドがブロックされているように見えますが、なぜそれが発生するのかわかりません。

UPDATE 1 偽共有が問題になると本当に思ったので、ループが計算値をスレッドローカル配列に格納するようにコードを書き直し、最後にこれらの配列を共有配列にコピーします。残念ながら、私自身はほとんど信じていませんが、これは何の影響も及ぼしませんでした。

@cmeerwのアドバイスに従って、strace -fを実行しました。すべての初期化の後、数百万行の行があります。

[pid 58067] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58066] <... futex resumed> )       = -1 EAGAIN (Resource temporarily unavailable)
[pid 58065] <... futex resumed> )       = -1 EAGAIN (Resource temporarily unavailable)
[pid 57684] <... futex resumed> )       = 0
[pid 58067] <... futex resumed> )       = 0
[pid 58066] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58065] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58067] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58066] <... futex resumed> )       = 0
[pid 57684] futex(0x35ca58bb40, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 58065] <... futex resumed> )       = 0
[pid 58067] <... futex resumed> )       = 0
[pid 57684] <... futex resumed> )       = -1 EAGAIN (Resource temporarily unavailable)
[pid 58066] futex(0x35ca58bb40, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 58065] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58066] <... futex resumed> )       = -1 EAGAIN (Resource temporarily unavailable)
[pid 57684] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58065] <... futex resumed> )       = 0
[pid 58066] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 57684] <... futex resumed> )       = 0
[pid 58067] futex(0x35ca58bb40, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 58066] <... futex resumed> )       = 0
[pid 58065] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58067] <... futex resumed> )       = -1 EAGAIN (Resource temporarily unavailable)
[pid 58066] futex(0x35ca58bb40, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 57684] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58065] <... futex resumed> )       = 0
[pid 58067] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58066] <... futex resumed> )       = -1 EAGAIN (Resource temporarily unavailable)
[pid 57684] <... futex resumed> )       = 0
[pid 58067] <... futex resumed> )       = 0
[pid 58066] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58065] futex(0x35ca58bb40, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 58066] <... futex resumed> )       = 0
[pid 58065] <... futex resumed> )       = -1 EAGAIN (Resource temporarily unavailable)
[pid 58066] futex(0x35ca58bb40, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 57684] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 58067] futex(0x35ca58bb40, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 58066] <... futex resumed> )       = -1 EAGAIN (Resource temporarily unavailable)
[pid 58065] futex(0x35ca58bb40, FUTEX_WAKE_PRIVATE, 1 <unfinished ...>
[pid 57684] <... futex resumed> )       = 0

誰かが何を意味するのか考えがありますか?スレッドがコンテキスト切り替えを頻繁に行っているように見えますか、それとも単にブロックとブロック解除を行っているだけですか?strace同じ実装OMP_NUM_THREADSを0に設定すると、これはまったく得られません。比較のために、1スレッド使用時に生成されるログファイルは486 KBであり、4スレッド使用時に生成されるログファイルは266MBです。

言い換えると、並列バージョンは、追加の4170104行のログファイルを呼び出します。

更新2

トムが提案したように、私はスレッドを特定のプロセッサにバインドしようとしましたが、役に立ちませんでした。OpenMP 3.1を使用しているので、を使用して環境変数を設定しますexport OMP_PROC_BIND=true。同じサイズのログファイルと同じ時間枠。

更新3

プロットが厚くなります。これまでクラスターでプロファイルを作成しただけで、Macportsを介してGNU GCC 4.7をインストールし、Macbookで初めてコンパイルしました(AppleのGCC-4.2.1は、OpenMPが有効になっているときにコンパイラーのバグをスローします。そのため、私はこれまで、ローカルでコンパイルして並列実行していませんでした)。Macbookでは、基本的にあなたが期待する傾向が見られます

                C-code time
 Serial:         ~34 seconds
 2 thrds:        ~21 seconds
 4 thrds:        ~14 seconds
 8 thrds:        ~12 seconds
16 thrds:         ~9 seconds

このテストデータで反復しているいくつかのデータセットのメンバーが16未満であるため、これは驚くことではありませんが、終わりに向かってリターンが減少していることがわかります(したがって、たとえばfor-loop7回の反復で16個のスレッドを生成しています) 。

それで、今、疑問が残ります-なぜクラスターのパフォーマンスがそれほどひどく低下するのですか?今夜は別のクアッドコアlinuxboxを試してみます。クラスターはGNU-GCC4.6.3でコンパイルされますが、それ自体がそのような違いを生むとは信じられませんか?

どちらltraceGDBクラスターにインストールされていません(そして、さまざまな理由でそれらをインストールできません)。linuxboxがクラスターのようなパフォーマンスを提供する場合は、そこで対応するltrace分析を実行します。

更新4

ああ、私の。Macbook ProをUbuntu(12.04)でデュエルブートし、コードを再実行しました。すべて実行されますが(これは多少安心です)、クラスターで見られるのと同じ、奇妙なパフォーマンスの悪い動作と、何百万ものfutex呼び出しの同じ実行が見られます。UbuntuとOSXのローカルマシンの唯一の違いはソフトウェアです(そして私は同じコンパイラとライブラリを使用しています-おそらくglibcOSXとUbuntuの実装に違いはありません!)これは何かLinuxがスレッドをスケジュール/配布する方法を使用します。いずれにせよ、ローカルマシンを使用すると、すべてが100万倍簡単になるので、先に進んltrace -fで、何が見つかるかを確認します。クラスターの回避策を作成しました。forks()別のプロセスをオフにして、ランタイムで完璧な1/2を提供するので、並列処理を実行することは間違いなく可能です...

4

3 に答える 3

8

したがって、プロファイリング用の本番レベルのコードを生成するための大きなラッパー関数を作成することを含む、かなり広範なプロファイリング(gprofとgdbを使用した時間サンプリングに関するこのすばらしい投稿のおかげで)の後、私がgdbを使用して実行中のコードを中止しbacktrace、スタックを実行してSTL <vector>、何らかの方法でベクターを操作しました。

コードは、いくつかのベクトルをparallelプライベート変数としてセクションに渡しますが、これは正常に機能しているようです。しかし、すべてのベクトルを引き出して配列(およびそれを機能させるための他のジゲリーポケリー)に置き換えた後、私は大幅なスピードアップを見ました。小さな人工データセットの場合、スピードアップはほぼ完璧です(つまり、半分の時間でスレッド数を2倍にするため)。実際のデータセットの場合、スピードアップはそれほど良くありませんが、これはコンテキストと同様に完全に理にかなっています。コードがどのように機能するかについて。

何らかの理由で(おそらくSTL<vector>実装の奥深くにある静的変数またはグローバル変数?)、何十万もの反復を並行して移動するループがある場合、Linux(Ubuntu12.01およびCentOS6.2)で発生する深いレベルのロックがあるようです。しかし、OSXではありません。

なぜこの違いが見られるのか、私は本当に興味をそそられます。STLの実装方法に違いがあるのでしょうか(OSXバージョンはLinuxのものと同様にGNU GCC 4.7でコンパイルされた)、またはこれはコンテキスト切り替えに関係しているのでしょうか(Arne Babenhauserheideによって提案されたように)

要約すると、私のデバッグプロセスは次のとおりです。

  • R問題を特定するための内部からの最初のプロファイリング

  • static共有変数として機能する変数がないことを確認しました

  • でプロファイルさstrace -fltrace -f、ロックが原因であると特定するのに非常に役立ちました

  • valgrindエラーを探すためにプロファイル

  • スケジュールタイプ(自動、ガイド付き、静的、動的)とチャンクサイズのさまざまな組み合わせを試しました。

  • スレッドを特定のプロセッサにバインドしようとしました

  • 値のスレッドローカルバッファを作成して誤った共有を回避し、最後に単一の同期イベントを実装しますfor-loop

  • mallocing並列領域内のすべてのおよびを削除しましたfreeing-問題の解決にはなりませんでしたが、一般的なスピードアップはわずかでした

  • さまざまなアーキテクチャとOSを試してみましたが、最終的にはあまり役に立ちませんでしたが、これはLinuxとOSXの問題であり、スーパーコンピューターとデスクトップの問題ではないことを示しました。

  • 呼び出しを使用して並行性を実装するバージョンを構築するfork()-2つのプロセス間にワークロードを持たせる。これにより、OSXとLinuxの両方で時間が半分になりました。これは良かったです。

  • 本番データの負荷を複製するためのデータシミュレータを構築しました

  • gprofプロファイリング

  • gdbタイムサンプリングプロファイリング(中止およびバックトレース)

  • ベクトル演算をコメントアウトする

  • これが機能しなかった場合、Arne Babenhauserheideのリンクには、OpenMPでのメモリの断片化の問題に関する重要な情報が含まれている可能性があります。

于 2012-06-20T15:15:02.397 に答える
4

重要なプロファイリングなしで何が起こっているのかを確実に知ることは困難ですが、パフォーマンス曲線は偽共有を示しているようです...

スレッドは異なるオブジェクトを使用しますが、それらのオブジェクトはメモリ内でたまたま同じキャッシュラインに収まるほど近くにあり、キャッシュシステムは、1つのコアのみが保持できるハードウェア書き込みロックによって効果的に保護される単一の塊として扱います。時間

ドブス博士のトピックに関する素晴らしい記事

http://www.drdobbs.com/go-parallel/article/217500206?pgno=1

特に、ルーチンが多くのmalloc / freeを実行しているという事実は、これにつながる可能性があります。

1つの解決策は、デフォルトのアロケータではなくプールベースのメモリアロケータを使用して、各スレッドが異なる物理アドレス範囲からメモリを割り当てる傾向があるようにすることです。

于 2012-06-07T20:18:57.710 に答える
2

スレッドは実際には相互作用しないため、コードをマルチプロセッシングに変更するだけで済みます。最後にメッセージが渡されるだけで、スレッドが何も同期する必要がないことが保証されます。

これが基本的にそれを行うpython3.2コードです(パフォーマンス上の理由からPythonでは実行したくないでしょう-またはforループをC関数に入れてcython経由でバインドします。コードからわかりますとにかくPythonで表示する理由):

from concurrent import futures
from my_cython_module import huge_function
parameters = range(ntest)
with futures.ProcessPoolExecutor(4) as e:
    results = e.map(huge_function, parameters)
    shared_array = list(results)

それでおしまい。プロセスの数をクラスターに入れることができるジョブの数に増やし、各プロセスがジョブを送信および監視するだけで、任意の数の呼び出しにスケーリングできるようにします。

相互作用のない巨大な関数と小さな入力値は、ほとんどマルチプロセッシングを必要とします。そして、それができたらすぐに、MPI(ほぼ無制限のスケーリング)に切り替えるのはそれほど難しくありません。

技術的な面では、LinuxのAFAIKコンテキストスイッチは非常に高価ですが(カーネルスペースメモリが多いモノリシックカーネル)、OSXまたはHurd(Machマイクロカーネル)でははるかに安価です。これは、Linuxでは見られるがOSXでは見られない膨大なシステム時間を説明している可能性があります。

于 2012-06-15T13:40:53.557 に答える