bugfix> c++ > 投稿

現在、私はアルゴリズムをラプラス方程式に実装しようとしています。私はいくつかの実装を見てきましたが、OpenMPのプラグマ宣言を配置するのに最適な場所にこだわっています。

while( var > tol && iter <= maxIter ) {
    ++iter;
    var = 0.0;
    for (i=1; i<=n; ++i)
        for (j=1; j<=n; ++j) {         
            Tnew[i*n2+j] = 0.25*( T[(i-1)*n2+j] + T[(i+1)*n2+j] 
                                + T[i*n2+(j-1)] + T[i*n2+(j+1)] );
            var = fmax(var, fabs(Tnew[i*n2+j] - T[i*n2+j]));
        }
}

個人的には、依存関係の問題は発生しないと考えているため、最良のケースは内部ループの直前に配置することだと考えています。しかし、友人は私にそれはあまりにも高価だろうと言った。これに取り組む最善の方法は何ですか?その理由は何ですか?

回答 1 件
  • まず、配列 Tnew を交換するのを忘れたと思います  および T  すべての外側ループの繰り返しの最後。 これらがポインターであると仮定すると、修正されたシリアルコードは次のとおりです。

    while(var > tol && iter <= maxIter) {
        ++iter;
        var = 0.0;
        for (i=1; i<=n; ++i)
            for (j=1; j<=n; ++j) {         
                Tnew[i*n2+j] = 0.25*(T[(i-1)*n2+j] + T[(i+1)*n2+j] 
                                    + T[i*n2+(j-1)] + T[i*n2+(j+1)]);
                var = fmax(var, fabs(Tnew[i*n2+j] - T[i*n2+j]));
            }
        }
        temp = TNew;
        TNew = T;
        T = temp;
    }
    
    

    今、あなたは i の並列化を試みたいかもしれません -ループ。 var  共有変数である必要があるため、最も内側の並列領域で更新が競合します。 critical を使用します  原子性を確保するために構築します。ナイーブコードは次のとおりです。

    while(var > tol && iter <= maxIter) {
        ++iter;
        var = 0.0;
        #pragma omp parallel for
        for (i=1; i<=n; ++i)
            for (j=1; j<=n; ++j) {         
                Tnew[i*n2+j] = 0.25*(T[(i-1)*n2+j] + T[(i+1)*n2+j] 
                                    + T[i*n2+(j-1)] + T[i*n2+(j+1)]);
                #pragma omp critical
                {
                    var = fmax(var, fabs(Tnew[i*n2+j] - T[i*n2+j]));
                }
            }
        }
        temp = TNew;
        TNew = T;
        T = temp;
    }
    
    

    あなたの友人が正しく指摘したように、スレッドの作成/終了のオーバーヘッドは、 parallel  リージョンは何度も作成および破棄されます。したがって、 master の助けを借りて  または single  コンストラクト、この並列コンストラクトを最も外側のループの外側に移動しようとする必要があります。翻訳の例を次に示します。

    #pragma omp parallel private(iter) shared(var, maxIter)
    {   
        while(var > tol && iter <= maxIter) {
            ++iter;
            #pragma omp barrier
            #pragma omp single
            {
                var = 0.0;
            }
            #pragma omp for
            for (i=1; i<=n; ++i)
                for (j=1; j<=n; ++j) {         
                    Tnew[i*n2+j] = 0.25*(T[(i-1)*n2+j] + T[(i+1)*n2+j] 
                                        + T[i*n2+(j-1)] + T[i*n2+(j+1)]);
                    #pragma omp critical
                    {
                        var = fmax(var, fabs(Tnew[i*n2+j] - T[i*n2+j]));
                    }
                }
            }
            #pragma omp single
            {
                temp = TNew;
                TNew = T;
                T = temp;
            }
        }
    }
    
    

    iter を民営化することが重要であることに注意してください 。また、明示的および暗黙的な障壁により、 var への異なるアクセス間で競合が発生しないことにも注意してください。 、 TNew  および T

    critical について  構成する: 最も内側のループ内で何度もクリティカル領域を呼び出すと、コストがかかることに注意してください。プライベート変数を使用して各スレッドの最大差を取得し、クリティカル領域を使用する必要があります外側  var の正しい値を取得するためのforループ 。実際の翻訳はあなたにお任せします。

    提案:いくつかの大きなデータセット、つまり n の大きな値に対してこのコードをテストします  (約500-2000)および tol の小さな値  (約0.01-0.001)、シリアルコードと比較して目に見える高速化を確認します。そうしないと、同期コストによって並列処理のゲインが無効になり、並列バージョンの効率がシリアルバージョンよりも低下する可能性があります。

あなたの答え