2

2つの配列を合計し、3番目の配列を出力しています(縮小ではありません)。このような:

void add_scalar(float* result, const float* a, const float* b, const int N) {   
    for(int i = 0; i<N; i++) {
        result[i] = a[i] + b[i];
    }
}

最大のスループットでこれを実行したいと思います。SSEと4つのコアを使用すると、単純に16倍の速度向上が期待できます(SSEの場合は4つ、4つのコアの場合は4つ)。SSE(およびAVX)を使用してコードを実装しました。Visual Studio 2012には自動ベクトル化がありますが、「ループを展開する」ことでより良い結果が得られます。L1、L2、L3キャッシュ、およびメインメモリに対応する32KB未満、256KB未満、8MB未満、および8 MBを超えるコアの4つのサイズの配列(32バイトアライメント)のコードを実行します。L1の場合、展開されたSSEコード(AVXでは5〜6)を使用すると約4倍のスピードアップが見られます。それは私が期待する限りです。その後、キャッシュレベルごとに効率が低下します。次に、OpenMPを使用して各コアで実行します。配列のメインループの前に「#pragmaompparallelfor」を配置しました。ただし、私が得る最高のスピードアップは、SSE + OpenMPで5〜6倍です。16倍のスピードアップが見られない理由を誰かが知っていますか?システムメモリからキャッシュへのアレイの「アップロード」時間が原因である可能性がありますか?コードのプロファイルを作成する必要があることはわかっていますが、それ自体が別の冒険であり、学ぶ必要があります。

#define ROUND_DOWN(x, s) ((x) & ~((s)-1))  
void add_vector(float* result, const float* a, const float* b, const int N) {
    __m128 a4;
    __m128 b4;
    __m128 sum;
    int i = 0;
    for(; i < ROUND_DOWN(N, 8); i+=8) {
        a4 = _mm_load_ps(a + i);
        b4 = _mm_load_ps(b + i);
        sum = _mm_add_ps(a4, b4);
        _mm_store_ps(result + i, sum);
        a4 = _mm_load_ps(a + i + 4);
        b4 = _mm_load_ps(b + i + 4);
        sum = _mm_add_ps(a4, b4);
        _mm_store_ps(result + i + 4, sum);
    }
    for(; i < N; i++) {
        result[i] = a[i] + b[i];
    }
    return 0;
}

次のような競合状態の私の間違ったメインループ:

float *a = (float*)_aligned_malloc(N*sizeof(float), 32);
float *b = (float*)_aligned_malloc(N*sizeof(float), 32);
float *c = (float*)_aligned_malloc(N*sizeof(float), 32);
#pragma omp parallel for
for(int n=0; n<M; n++) {  //M is an integer of the number of times to run over the array
    add_vector(c, a, b, N);
}

Grizzlyの提案に基づいて修正したメインループ:

for(int i=0; i<4; i++) {
    results[i] = (float*)_aligned_malloc(N*sizeof(float), 32);
}
#pragma omp parallel for num_threads(4)
for(int t=0; t<4; t++) {
    for(int n=0; n<M/4; n++) { //M is an integer of the number of times to run over the array
        add_vector(results[t], a, b, N);
    }
}
4

1 に答える 1

6

免責事項:あなたと同じように、私はコードのプロファイルを作成していないため、絶対的な確信を持って答えることができません.

あなたの問題は、メモリ帯域幅または並列化のオーバーヘッドに関連している可能性が最も高いです。

ループは 3 つのメモリ操作に対して 1 つの追加を行うため、非常に計算量が少なく、メモリ帯域幅によって自然に制限されます (ALU スループットは、最新のアーキテクチャのメモリ帯域幅よりもはるかに優れていると考えてください)。したがって、ほとんどの時間はデータの転送に費やされます。

データがキャッシュに収まるほど小さい場合、(理論的には) openmp スレッドを特定のコアにバインドし、ベクトルの正しい部分が特定のコアの L1/L2 キャッシュにあることを確認できますが、それは実際には役に立ちません。初期化を並列化できない限り (とにかくデータを転送する必要がある場合は、いつデータを転送するかは問題ではありません)。したがって、あるコアキャッシュから別のコアキャッシュにデータを転送することでヒットしています。

データがプロセッサ キャッシュに収まらない場合、最終的にはメイン メモリへの帯域幅によって制限されます。1 つのコアをプリフェッチすることで、このような簡単なアクセス パターンの帯域幅をほぼ最大にできる可能性があり、成長の余地がほとんどなくなります。

覚えておくべき 2 番目のポイントは、構造の作成とomp parallelループの分散には、ある程度のオーバーヘッドがあるということです。小規模なデータセット (L1/L2/L3 に適合するデータセットはおそらく資格があります) の場合、このオーバーヘッドは計算時間自体と同じくらい簡単に高くなり、スピードアップはほとんどまたはまったくありません。

于 2013-03-07T13:56:29.287 に答える