2

組み込み関数を使用してすでにベクトル化されたコードの並列化にOpenMPを使用しようとしていますが、問題は、各ループをインクリメントする外部の「変数」として1つのXMMレジスタを使用していることです。今のところ私はshared句を使用しています

__m128d xmm0 = _mm_setzero_pd();
__declspec(align(16)) double res[2];

#pragma omp parallel for shared(xmm0)
for (int i = 0; i < len; i++)
{
    __m128d xmm7 = ... result of some operations

    xmm0 = _mm_add_pd(xmm0, xmm7);
}

_mm_store_pd(res, xmm0);
double final_result = res[0] + res[1];

操作がサポートされていないためatomic(VS2010)

__m128d xmm0 = _mm_setzero_pd();
__declspec(align(16)) double res[2];

#pragma omp parallel for
for (int i = 0; i < len; i++)
{
    __m128d xmm7 = ... result of some operations

    #pragma omp atomic
    xmm0 = _mm_add_pd(xmm0, xmm7);
}

_mm_store_pd(res, xmm0);
double final_result = res[0] + res[1];

誰かが賢い回避策を知っていますか?


編集:私は今、並列パターンライブラリを使用してそれを試しました:

__declspec(align(16)) double res[2];
combinable<__m128d> xmm0_comb([](){return _mm_setzero_pd();});

parallel_for(0, len, 1, [&xmm0_comb, ...](int i)
{
    __m128d xmm7 = ... result of some operations

    __m128d& xmm0 = xmm0_comb.local();
    xmm0 = _mm_add_pd(xmm0, xmm7);
});

__m128d xmm0 = xmm0_comb.combine([](__m128d a, __m128d b){return _mm_add_pd(a, b);});
_mm_store_pd(res, xmm0);
double final_result = res[0] + res[1];

しかし、結果は期待外れでした。

4

4 に答える 4

4

あなたは間違った方法で問題を解決しています。アトミック操作の代わりに削減を使用する必要があります。

これはより良いアプローチです:

double sum = 0;

#pragma omp parallel for reduction(+:sum)
for (int i = 0; i < len; i++)
{
    __m128d xmm7;// = ... result of some operations

    //  Collapse to a "double".
    _declspec(align(16)) double res[2];
    _mm_store_pd(res, xmm7);

    //  Add to reduction variable.
    sum += res[0] + res[1];
}

double final_result = sum;

リダクションは基本的に、 などの連想操作を使用してすべてを 1 つの変数に「縮小」する操作+です。

リダクションを行う場合は、常に実際のリダクション アプローチを使用するようにしてください。アトミック操作やクリティカル セクションでごまかそうとしないでください。

この理由は、アトミック/クリティカル セクションのアプローチは、長いクリティカル パスのデータ依存性を維持するため、本質的にスケーラブルではないためです。適切な削減アプローチは、このクリティカル パスを に削減しlog(# of threads)ます。

もちろん、唯一の欠点は、浮動小数点の結合性が損なわれることです。それが重要な場合は、基本的に各反復を順番に合計することに固執しています。

于 2012-12-23T22:17:22.927 に答える
2

私の質問に答えてくれた人々からの多大な助けを借りて、私はこれを思いつきました:

double final_result = 0.0;

#pragma omp parallel reduction(+:final_result)
{
    __declspec(align(16)) double r[2];
    __m128d xmm0 = _mm_setzero_pd();

    #pragma omp for
    for (int i = 0; i < len; i++)
    {
        __m128d xmm7 = ... result of some operations

        xmm0 = _mm_add_pd(xmm0, xmm7);
    }
    _mm_store_pd(r, xmm0);
    final_result += r[0] + r[1];
}

基本的に崩壊と縮小を分離し、非常にうまく機能します。

私を助けてくれたすべての人に感謝します!

于 2012-12-23T23:01:01.167 に答える
2

あなたが探しているのは削減です。コンパイラがサポートしている場合 (gcc がサポートしている場合)、omp リダクションとしてこれを行うことができます。または、スレッドごとにプライベート xmm に合計して自分でロールすることもできます。以下は、両方を行う単純なバージョンです。

#include <emmintrin.h>
#include <omp.h>
#include <stdio.h>


int main(int argc, char **argv) {

    const int NTHREADS=8;
    const int len=100;

    __m128d xmm0[NTHREADS];
    __m128d xmmreduction = _mm_setzero_pd();
    #pragma omp parallel for num_threads(NTHREADS)
    for (int i=0; i<NTHREADS; i++)
        xmm0[i]= _mm_setzero_pd();

    __attribute((aligned(16))) double res[2];

    #pragma omp parallel num_threads(NTHREADS) reduction(+:xmmreduction)
    {
        int tid = omp_get_thread_num();
        #pragma omp for
        for (int i = 0; i < len; i++)
        {
            double d = (double)i;
            __m128d xmm7 = _mm_set_pd( d, 2.*d );

            xmm0[tid] = _mm_add_pd(xmm0[tid], xmm7);
            xmmreduction = _mm_add_pd(xmmreduction, xmm7);
        }
    }

    for (int i=1; i<NTHREADS; i++)
        xmm0[0] = _mm_add_pd(xmm0[0], xmm0[i]);

    _mm_store_pd(res, xmm0[0]);
    double final_result = res[0] + res[1];

    printf("Expected result   = %f\n", 3.0*(len-1)*(len)/2);
    printf("Calculated result = %lf\n", final_result);

    _mm_store_pd(res, xmmreduction);
    final_result = res[0] + res[1];

    printf("Calculated result (reduction) = %lf\n", final_result);

    return 0;
}
于 2012-12-23T22:29:25.253 に答える
0

独自の組み込み関数をコンパイラに追加することはできないと思います.MSコンパイラはインラインアセンブラをスキップすることにしました. 簡単な解決策があるかどうかはわかりません。

于 2012-12-23T21:45:02.777 に答える