あなたのシミュレーションタイプの詳細がなければ、私は推測することしかできないので、ここに私の推測があります。
負荷分散の問題を調べましたか?ループは粒子をスレッド間で分散すると思いますが、ある種の制限された範囲の可能性がある場合、計算時間は、空間密度に応じて、シミュレーションボリュームのさまざまな領域で粒子ごとに異なる可能性があります。これは分子動力学で非常に一般的な問題であり、分散メモリ(ほとんどの場合MPI)コードで適切に解決するのは非常に困難です。幸い、OpenMPを使用すると、各コンピューティング要素のすべてのパーティクルに直接アクセスできるため、負荷分散をはるかに簡単に実現できます。簡単であるだけでなく、いわば組み込みでもあります。for
ディレクティブのスケジュールをschedule(dynamic,chunk)
句で変更するだけです。chunk
は少数であり、その最適値はシミュレーションごとに異なる可能性があります。chunk
プログラムへの入力データの一部を作成するか、代わりに、環境変数を、、、などの値に設定することschedule(runtime)
により、さまざまなスケジューリングクラスを記述して操作することができます。OMP_SCHEDULE
"static"
"dynamic,1"
"dynamic,10"
"guided"
パフォーマンス低下のもう1つの考えられる原因は、偽共有と真共有です。データ構造が同時変更に適していない場合、偽共有が発生します。たとえば、各粒子の3D位置および速度情報を保持する場合(たとえば、速度Verlet積分器を使用する場合)、IEEE 754の倍精度が与えられると、各座標/速度トリプレットは24バイトを使用します。これは、64バイトの単一のキャッシュラインが2つの完全なトリプレットと別のトリプレットの2/3を収容することを意味します。この結果、スレッド間でパーティクルをどのように分散しても、キャッシュラインを共有する必要があるスレッドは常に少なくとも2つ存在します。それらのスレッドが異なる物理コアで実行されていると仮定します。1つのスレッドがキャッシュラインのコピーに書き込む場合(たとえば、パーティクルの位置を更新する場合)、キャッシュコヒーレンシプロトコルが関与し、他のスレッドのキャッシュラインを無効にします。これにより、メインメモリからでも低速のキャッシュからキャッシュラインを再読み取りする必要があります。2番目のスレッドがそのパーティクルを更新すると、最初のコアのキャッシュラインが無効になります。この問題の解決策は、2つのスレッドが単一のキャッシュラインを共有しないように、適切なパディングと適切なチャンクサイズの選択を伴うことです。たとえば、表面的な4番目の次元を追加する場合(これを使用して、粒子の位置エネルギーを位置ベクトルの4番目の要素に格納し、運動エネルギーを速度ベクトルの4番目の要素に格納できます)その場合、各位置/速度の4つ組は32バイトを取り、正確に2つの粒子の情報が1つのキャッシュラインに収まります。次に、スレッドごとに偶数のパーティクルを分散させると、
真の共有は、スレッドが同じデータ構造に同時にアクセスし、異なるスレッドによって変更された構造の部分の間にオーバーラップがある場合に発生します。分子動力学シミュレーションでは、ペアワイズ相互作用ポテンシャルを処理するときに計算時間を2に短縮するために、ニュートンの第3法則を利用したいため、これは非常に頻繁に発生します。1つのスレッドが粒子i
に作用する力を計算し、その隣接するスレッドを列挙する場合、に作用する力を自動的に計算すると、に作用する力が得られるj
ため、に作用する力の合計に寄与を加えることができます。だがj
i
i
j
j
j
同時に変更する可能性のある別のスレッドに属している可能性があるため、両方の更新にアトミック操作を使用する必要があります(両方とも、別のスレッドが更新される可能性があります)i
それがそれ自身の粒子の1つ以上に隣接する場合)。x86のアトミック更新は、ロックされた命令で実装されます。これは、頻繁に提示されるほどひどく遅いわけではありませんが、それでも通常の更新よりも遅いです。また、偽共有の場合と同じキャッシュライン無効化効果も含まれています。これを回避するには、メモリ使用量の増加を犠牲にして、ローカル配列を使用して部分的な力の寄与を格納し、最終的に削減を実行できます。削減自体は、ロックされた命令とシリアルまたはパラレルで実行する必要があるため、このアプローチを使用してもメリットがないだけでなく、さらに遅くなる可能性があります。この問題に取り組むために、適切な粒子の選別と処理要素間の巧妙な分配を使用して、界面領域を最小限に抑えることができます。
もう1つ触れておきたいのは、メモリ帯域幅です。アルゴリズムに応じて、フェッチされるデータ要素の数とループの各反復で実行される浮動小数点演算の数の間には一定の比率があります。各プロセッサのメモリフェッチに使用できる帯域幅は限られており、データがCPUキャッシュに完全に収まらない場合は、メモリバスが、単一で実行されている非常に多くのスレッドにフィードするのに十分なデータを配信できない可能性があります。ソケット。Corei3-2370Mには3MiBのL3キャッシュしかないため、各パーティクルの位置、速度、力を明示的に保持すると、L3キャッシュに保存できるパーティクルは約43000個、L2キャッシュに保存できるパーティクルは約3600個(または約1800個)になります。ハイパースレッドあたりのパーティクル)。
最後はハイパースレッディングです。ハイパフォーマンスマークがすでに指摘しているように、ハイパースレッドは多くのコア機構を共有しています。たとえば、両方のハイパースレッド間で共有されるAVXベクトルFPUエンジンは1つだけです。コードがベクトル化されていない場合、プロセッサで利用できる計算能力が大幅に失われます。コードがベクトル化されている場合、両方のハイパースレッドは、AVXエンジンを制御するために戦うときに、互いに入り込みます。ハイパースレッディングは、(あるハイパースレッドでの)計算を(別のハイパースレッドでの)メモリ負荷とオーバーレイすることによってメモリレイテンシを隠すことができる場合にのみ役立ちます。メモリのロード/ストアを実行する前に多くのレジスタ操作を実行する高密度の数値コードでは、ハイパースレッディングは何のメリットもありません。dスレッドの数を半分にして実行し、OSスケジューラーがスレッドをハイパースレッドとして実行しないように、それらを異なるコアに明示的にバインドすることをお勧めします。Windowsのスケジューラーは、この点で特に馬鹿げています。を参照してください。暴言の例については、こちらをご覧ください。IntelのOpenMP実装は、環境変数を介して制御されるさまざまなバインディング戦略をサポートします。GNUのOpenMP実装も。MicrosoftのOpenMP実装でスレッドバインディング(別名アフィニティマスク)を制御する方法を私は知りません。