2

このコードはOpenMPでは遅くなります。OpenMPがないと、約10秒かかります。OpenMPを使用すると、約40秒になります。何が起こっている?友達ありがとうございます!

for (i=2;i<(nnoib-2);++i){
    #pragma omp parallel for
    for (j=2; j<(nnojb-2); ++j) {
        C[i][j]= absi[i]*absj[j]*
                 (2.0f*B[i][j] + absi[i]*absj[j]*
                 (VEL[i][j]*VEL[i][j]*fat*
                 (16.0f*(B[i][j-1]+B[i][j+1]+B[i-1][j]+B[i+1][j])
                 -1.0f*(B[i][j-2]+B[i][j+2]+B[i-2][j]+B[i+2][j]) 
                 -60.0f*B[i][j]
                 )-A[i][j]));
        c2 = (abs(C[i][j]) > Amax[i][j]);
        if (c2) {
            Amax[i][j] = abs(C[i][j]);
            Ttra[i][j] = t;
        }
    }
}
4

2 に答える 2

3

OpenMP を使用しているからといって、プログラムが高速に実行されるわけではありません。ここでいくつかのことが起こっている可能性があります。

  1. 各スレッドの生成にはコストがかかります。スレッドを生成して少量の計算を行うと、スレッド自体の生成に計算よりも多くの時間がかかります。

  2. デフォルトでは、OpenMP は CPU がサポートする最大数のスレッドを生成します。コアごとに 2 つ以上のスレッドをサポートする CPU では、スレッドは各コアのリソースをめぐって競合します。を使用omp_get_num_threads()すると、デフォルトで生成されるスレッドの数を確認できます。を使用して、その半分の値でコードを実行することをお勧めしますomp_set_num_threads()

OpenMP の有無にかかわらず、結果が同じであることを確認しましたか? 変数 j と c2 には依存関係があるようです。それらを各スレッドに対してプライベートに宣言する必要があります。

#pragma omp parallel for private(j,c2)

もう 1 つ付け加えておきたいことがあります。並列化を試みる前に、コードが既に最適化されていることを確認する必要があります。

コンパイラ、コンパイラ フラグ、および命令の複雑さに応じて、コンパイラはコードを最適化する場合としない場合があります。

// avoid calculation nnoib-2 every iteration
int t_nnoib = nnoib - 2;
for (i=2; i< t_nnoib; ++i){
    // avoid calculation nnojb-2 every iteration
    int t_nnojb = nnojb - 2;
    // avoid loading absi[i] every iteration
    int t_absi = absi[i];
    for (j=2; j< t_nnojb; ++j) {
        C[i][j]= t_absi * absj[j] *
             (2.0f*B[i][j] + t_absi * absj[j] *
             (VEL[i][j] * VEL[i][j] * fat *
             (16.0f * (B[i][j-1] + B[i][j+1] + B[i-1][j] + B[i+1][j])
              -1.0f * (B[i][j-2] + B[i][j+2] + B[i-2][j] + B[i+2][j]) 
              -60.0f * B[i][j]
             ) - A[i][j]));

        // c2 is a useless variable
        if (abs(C[i][j]) > Amax[i][j]) {
            Amax[i][j] = abs(C[i][j]);
            Ttra[i][j] = t;
         }
    }
}

大したことではないように思えるかもしれませんが、コードに大きな影響を与える可能性があります。コンパイラは、ローカル変数をレジスタに配置しようとします (アクセス時間がはるかに高速です)。レジスタの数が限られているため、この手法を無期限に適用することはできないことに注意してください。これを悪用すると、コードでレジスタ スピルが発生します。

array の場合、ループabsiの実行中にシステムがその配列の一部をキャッシュに保持することを回避できます。jこの手法の一般的な考え方は、内側のループの変数に依存しない配列アクセスを外側のループに移動することです。

于 2013-03-10T02:58:00.817 に答える
2

Cristiano が言及したコストに加えて、jループではなくループで並列化するという選択は、割り当てられている 3 つの配列で偽共有iのリスクをもたらします。基本的に、1 つのスレッドがこれらの配列の 1 つの要素に書き込むと、同じキャッシュ ライン上の連続する要素もそのコアのキャッシュに読み込まれます。次に、別のコアが独自の値を別のエントリに書き込もうとすると、複数のコアが「綱引き」を行う可能性がある状態で、他のキャッシュからラインをプルする必要があります。C, Amax, Ttra

iこれに対する解決策は、内側のループではなく外側のループを並列化することjです。i便利なことに、これにより、Cristiano の回答で言及されているコストも大幅に削減されます。これは、スポーンと作業の割り当てがループの反復ごとではなく 1 回だけ行われるためです。および をプライベート化するか、単に値をインライン化jし、変数を削除する必要があります (コメントで説明されているように)。効率を高めるために、代わりにローカルで宣言された変数を使用すると、スレッドプライベート変数にアクセスする必要がなくなります。c2c2ifj

(かなり重要な) チェックとして、このループ ネストは、実際には、プログラムの中で最も多くの時間を費やしていると測定された部分ですか? OpenMP プラグマを追加すると、時間が 10 秒弱から 40 秒弱に変わりましたか?

于 2013-03-10T07:40:53.553 に答える