OpenMP の黄金律は、外側のスコープで定義されたすべての変数 (いくつかの除外を含む) が、デフォルトで並列領域で共有されることです。2008 年より前の Fortran にはローカル スコープがないため (つまりBLOCK ... END BLOCK
、以前のバージョンにはありません)、すべての変数 (変数を除くthreadprivate
) が共有されます。これは私にとって非常に自然なことです (Ian Bush とは異なりdefault(none)
、さまざまな複雑な科学コードで 100 以上のローカル変数すべての可視性を再宣言します)。
各変数の共有クラスを決定する方法は次のとおりです。
N
- すべてのスレッドで同じにする必要があり、その値を読み取るだけであるため、共有されます。
ii
- ワークシェアリング ディレクティブの対象となるループのカウンターであるため、その共有クラスはprivate
. PRIVATE
句で明示的に宣言しても問題はありませんが、実際には必要ありません。
jj
jj
- ワークシェアリング ディレクティブの対象とならないループのループ カウンターは、したがってprivate
.
X
- すべてのスレッドが参照し、そこからのみ読み取るため、共有されます。
distance_vector
private
-各スレッドが異なるペアのパーティクルで動作するため、明らかにそうあるべきです。
distance
、distance2
、およびcoff
- 同上。
M
- と同じ理由で共有する必要がありますX
。
PE
-アキュムレータ変数として機能し(これはシステムのポテンシャルエネルギーだと思います)、リダクション操作の対象になる必要があります。つまり、REDUCTION(+:....)
節に入れる必要があります。
A
-これはトリッキーです。これは共有さA(jj,:)
れ、同期構造で保護されて更新されるか、リダクションを使用できます (OpenMP では、C/C++ とは異なり、Fortran では配列変数のリダクションが可能です)。A(ii,:)
複数のスレッドによって変更されることはないため、特別な処理は必要ありません。
リダクションを適切に行うとA
、各スレッドは のプライベート コピーを取得しA
、これはメモリを大量に消費する可能性がありますが、この直接的な O(N 2 ) シミュレーション コードを使用して、非常に多数の粒子を含むシステムを計算するとは思えません。リダクションの実装に関連する特定のオーバーヘッドもあります。この場合、句A
のリストに追加するだけです。REDUCTION(+:...)
同期構造には 2 つのオプションがあります。ATOMIC
コンストラクトまたはコンストラクトのいずれかを使用できますCRITICAL
。スカラー コンテキストにのみ適用されるためATOMIC
、代入ループを「ベクトル化解除」し、ATOMIC
各ステートメントに個別に適用する必要があります。次に例を示します。
!$OMP ATOMIC UPDATE
A(jj,1)=A(jj,1)+(M(ii)/coff)*(distance_vector(1))
!$OMP ATOMIC UPDATE
A(jj,2)=A(jj,2)+(M(ii)/coff)*(distance_vector(2))
!$OMP ATOMIC UPDATE
A(jj,3)=A(jj,3)+(M(ii)/coff)*(distance_vector(3))
これをループとして書き直すこともできます。ループ カウンターを宣言することを忘れないでくださいprivate
。
CRITICAL
ループをベクトル化解除する必要はありません。
!$OMP CRITICAL (forceloop)
A(jj,:)=A(jj,:)+(M(ii)/coff)*(distance_vector)
!$OMP END CRITICAL (forceloop)
重要な領域に名前を付けるのはオプションであり、この特定のケースでは少し不必要ですが、一般的には関連のない重要な領域を分離することができます。
どちらが速いですか?ATOMIC
またはCRITICAL
?で展開 それは多くのことに依存します。CRITICAL
アトミックインクリメントは、少なくとも x86 では、ロックされた加算命令で実装されますが、OpenMP ランタイムへの関数呼び出しが頻繁に含まれるため、通常はかなり遅くなります。彼らがよく言うように、YMMV。
要約すると、ループの作業バージョンは次のようになります。
!$OMP PARALLEL DO PRIVATE(jj,kk,distance_vector,distance2,distance,coff) &
!$OMP& REDUCTION(+:PE)
do ii=1,N-1
do jj=ii+1,N
distance_vector=X(ii,:)-X(jj,:)
distance2=sum(distance_vector*distance_vector)
distance=DSQRT(distance2)
coff=distance*distance*distance
PE=PE-M(II)*M(JJ)/distance
do kk=1,3
!$OMP ATOMIC UPDATE
A(jj,kk)=A(jj,kk)+(M(ii)/coff)*(distance_vector(kk))
end do
A(ii,:)=A(ii,:)-(M(jj)/coff)*(distance_vector)
end do
end do
!$OMP END PARALLEL DO
あなたのシステムは 3 次元であると仮定しました。
以上のことを踏まえて、私はイアン・ブッシュに、位置行列と加速度行列がメモリにどのように配置されるかを再考する必要があることを支持します。キャッシュを適切に使用すると、コードが向上し、X(:,ii)-X(:,jj)
ベクトル SIMD 命令を使用して実装されるベクトル化などの特定の操作が可能になります。