12

タスクは非常に単純で、一連の整数変数をメモリに書き込みます。

元のコード:

for (size_t i=0; i<1000*1000*1000; ++i)
{
   data[i]=i;
};

並列化されたコード:

    size_t stepsize=len/N;

#pragma omp parallel num_threads(N)
    {
        int threadIdx=omp_get_thread_num();

        size_t istart=stepsize*threadIdx;
        size_t iend=threadIdx==N-1?len:istart+stepsize;
#pragma simd
        for (size_t i=istart; i<iend; ++i)
            x[i]=i;
    };

パフォーマンスは最悪です。1Gの変数 (1 秒あたり 5GB に相当)を書き込むのに1.6 秒かかります。上記のコードの単純な並列化 ( ) によって、速度は少し向上しますが、パフォーマンスは依然として最悪です。4 スレッドで1.4 秒、1.35 秒かかりますi7 3970 で 6 スレッド。uint64open mp parallel

私のリグ ( i7 3970/64G DDR3-1600 ) の理論上のメモリ帯域幅は51.2 GB/secです。上記の例では、達成されたメモリ帯域幅は理論上の帯域幅の約1/10にすぎません。 -帯域幅制限。

コードを改善する方法を知っている人はいますか?

私は GPU で多くのメモリ バウンド コードを書きました。GPU が GPU のデバイス メモリ帯域幅 (理論帯域幅の 85%+ など) を最大限に活用するのは非常に簡単です。

編集:

コードは Intel ICC 13.1 によって 64 ビット バイナリにコンパイルされ、最大最適化 (O3) と AVX コード パス、および自動ベクトル化がオンになっています。

アップデート:

以下のすべてのコードを試しました (Paul R に感謝します)。特別なことは何も起こりません。コンパイラーは simd/ベクトル化の最適化を完全に行うことができると思います。

なぜそこに数字を入力したいのかというと、簡単に言えば:

高性能ヘテロジニアス コンピューティング アルゴリズムの一部であり、デバイス側では、マルチ GPU セットが非常に高速であるため、CPU が複数のシーケンスを書き込もうとするとパフォーマンスのボトルネックがたまたま発生することがわかったほど、アルゴリズムは非常に効率的です。メモリへの数字の。

当然のことながら、CPU は数値を埋めるのが苦手であることを知っています (対照的に、GPU は非常に近い速度で数のシーケンスを埋めることができます ( GK110 では 288GB/秒から238GB/秒に対して、GK110 では51.2GB/秒から5GB/) CPU) を GPU のグローバル メモリの理論上の帯域幅に変更する必要があるため、アルゴリズムを少し変更することもできますが、なぜ CPU がここで数列を埋めるのが苦手なのか不思議に思います。

私のリグのメモリ帯域幅に関しては、帯域幅 (51.2GB) はほぼ正しいと思います。私のmemcpy()テストに基づいて、達成された帯域幅は理論上の帯域幅 ( >40GB/秒) の約80%+です。

4

2 に答える 2

12

これが x86 であり、利用可能な DRAM 帯域幅をまだ飽和させていないと仮定すると、SSE2 または AVX2 を使用して一度に 2 つまたは 4 つの要素を書き込むことができます。

SSE2:

#include "emmintrin.h"

const __m128i v2 = _mm_set1_epi64x(2);
__m128i v = _mm_set_epi64x(1, 0);

for (size_t i=0; i<1000*1000*1000; i += 2)
{
    _mm_stream_si128((__m128i *)&data[i], v);
    v = _mm_add_epi64(v, v2);
}

AVX2:

#include "immintrin.h"

const __m256i v4 = _mm256_set1_epi64x(4);
__m256i v = _mm256_set_epi64x(3, 2, 1, 0);

for (size_t i=0; i<1000*1000*1000; i += 4)
{
    _mm256_stream_si256((__m256i *)&data[i], v);
    v = _mm256_add_epi64(v, v4);
}

data適切に整列する必要があることに注意してください(16 バイトまたは 32 バイト境界)。

AVX2 は Intel Haswell 以降でのみ利用できますが、SSE2 は最近ではほとんど普遍的です。


FWIW スカラー ループと上記の SSE および AVX ループを使用してテスト ハーネスをまとめ、clang でコンパイルし、Haswell MacBook Air (1600MHz LPDDR3 DRAM) でテストしました。次の結果が得られました。

# sequence_scalar: t = 0.870903 s = 8.76033 GB / s
# sequence_SSE: t = 0.429768 s = 17.7524 GB / s
# sequence_AVX: t = 0.431182 s = 17.6941 GB / s

また、3.6 GHz Haswell を搭載した Linux デスクトップ PC で試し、gcc 4.7.2 でコンパイルしたところ、次の結果が得られました。

# sequence_scalar: t = 0.816692 s = 9.34183 GB / s
# sequence_SSE: t = 0.39286 s = 19.4201 GB / s
# sequence_AVX: t = 0.392545 s = 19.4357 GB / s

そのため、SIMD 実装は 64 ビットのスカラー コードよりも 2 倍以上改善されているように見えます (ただし、256 ビット SIMD は 128 ビット SIMD よりも改善されていないようです)。通常のスループットは 5 GB /秒。

私の推測では、OP のシステムまたはベンチマーク コードに何か問題があり、明らかにスループットが低下していると思われます。

于 2013-08-23T13:13:36.947 に答える
5

data[]すべてがパワーアップした RAM ページにあると期待する理由はありますか?

DDR3 プリフェッチャーは、ほとんどのアクセスを正しく予測しますが、頻繁な x86-64 ページ境界が問題になる可能性があります。仮想メモリに書き込んでいるため、各ページ境界でプリフェッチャーの予測ミスが発生する可能性があります。大きなページを使用することで、これを大幅に減らすことができます ( MEM_LARGE_PAGESWindows など)。

于 2013-08-23T14:57:27.450 に答える