2017-11-04 7 views
1

私はAVXがSSEより約1.5倍速くなると予想していました。 Intel Core CPU(Broadwell)では、3つのアレイ(3つのアレイ* 16384要素* 4バイト/要素= 196608バイト)がL2キャッシュ(256KB)に収まる必要があります。AVX vs. SSE:より高速なスピードアップを期待する

使用するはずの特別なコンパイラ指令またはフラグはありますか?

コンパイラバージョン

$ clang --version 
Apple LLVM version 9.0.0 (clang-900.0.38) 
Target: x86_64-apple-darwin16.7.0 
Thread model: posix 
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin 

コンパイルライン

$ make avx 
clang -O3 -fno-tree-vectorize -msse -msse2 -msse3 -msse4.1 -mavx -mavx2 avx.c ; ./a.out 123 
n: 123 
    AVX Time taken: 0 seconds 177 milliseconds 
vector+vector:begin int: 1 5 127 0 

    SSE Time taken: 0 seconds 195 milliseconds 
vector+vector:begin int: 1 5 127 0 

avx.c

#include <stdio.h> 
#include <stdlib.h> 
#include <x86intrin.h> 
#include <time.h> 
#ifndef __cplusplus 
#include <stdalign.h> // C11 defines _Alignas(). This header defines alignas() 
#endif 
#define REPS 50000 
#define AR 16384 

// add int vectors via AVX 
__attribute__((noinline)) 
void add_iv_avx(__m256i *restrict a, __m256i *restrict b, __m256i *restrict out, int N) { 

    __m256i *x = __builtin_assume_aligned(a, 32); 
    __m256i *y = __builtin_assume_aligned(b, 32); 
    __m256i *z = __builtin_assume_aligned(out, 32); 

    const int loops = N/8; // 8 is number of int32 in __m256i 
    for(int i=0; i < loops; i++) { 
     _mm256_store_si256(&z[i], _mm256_add_epi32(x[i], y[i])); 
    } 
} 

// add int vectors via SSE; https://en.wikipedia.org/wiki/Restrict 
__attribute__((noinline)) 
void add_iv_sse(__m128i *restrict a, __m128i *restrict b, __m128i *restrict out, int N) { 

    __m128i *x = __builtin_assume_aligned(a, 16); 
    __m128i *y = __builtin_assume_aligned(b, 16); 
    __m128i *z = __builtin_assume_aligned(out, 16); 

    const int loops = N/sizeof(int); 
    for(int i=0; i < loops; i++) { 
     //out[i]= _mm_add_epi32(a[i], b[i]); // this also works 
     _mm_storeu_si128(&z[i], _mm_add_epi32(x[i], y[i])); 
    } 
} 

// printing 
void p128_as_int(__m128i in) { 
    alignas(16) uint32_t v[4]; 
    _mm_store_si128((__m128i*)v, in); 
    printf("int: %i %i %i %i\n", v[0], v[1], v[2], v[3]); 
} 

__attribute__((noinline)) 
void debug_print(int *h) { 
    printf("vector+vector:begin "); 
    p128_as_int(* (__m128i*) &h[0]); 
} 

int main(int argc, char *argv[]) { 
    int n = atoi (argv[1]); 
    printf("n: %d\n", n); 

    int *x,*y,*z; 
    if (posix_memalign((void**)&x, 32, 16384*sizeof(int))) { free(x); return EXIT_FAILURE; } 
    if (posix_memalign((void**)&y, 32, 16384*sizeof(int))) { free(y); return EXIT_FAILURE; } 
    if (posix_memalign((void**)&z, 32, 16384*sizeof(int))) { free(z); return EXIT_FAILURE; } 
    x[0]=0; x[1]=2; x[2]=4; 
    y[0]=1; y[1]=3; y[2]=n; 

    // touch each 4K page in x,y,z to avoid copy-on-write optimizations 
    for (int i=512; i<AR; i+= 512) { x[i]=1; y[i]=1; z[i]=1; } 

    // warmup 
    for(int i=0; i<REPS; ++i) { add_iv_avx((__m256i*)x, (__m256i*)y, (__m256i*)z, AR); } 
    // AVX 
    clock_t start = clock(); 
    for(int i=0; i<REPS; ++i) { add_iv_avx((__m256i*)x, (__m256i*)y, (__m256i*)z, AR); } 
    int msec = (clock()-start) * 1000/CLOCKS_PER_SEC; 
    printf(" AVX Time taken: %d seconds %d milliseconds\n", msec/1000, msec%1000); 
    debug_print(z); 

    // warmup 
    for(int i=0; i<REPS; ++i) { add_iv_sse((__m128i*)x, (__m128i*)y, (__m128i*)z, AR); } 
    // SSE 
    start = clock(); 
    for(int i=0; i<REPS; ++i) { add_iv_sse((__m128i*)x, (__m128i*)y, (__m128i*)z, AR); } 
    msec = (clock()-start) * 1000/CLOCKS_PER_SEC; 
    printf("\n SSE Time taken: %d seconds %d milliseconds\n", msec/1000, msec%1000); 
    debug_print(z); 

    return EXIT_SUCCESS; 
} 
+0

ループで数回繰り返し、SSEとAVXの間で順序を入れ替えます。最後に、あなたはあなたが期待している1.5にかなり近い比率をSkylakeで取得します。 –

+2

要素ごとの計算量が少ない(ちょうど追加のために)、私は実行時間が主にメモリに束縛されていると思います。 – EOF

+1

あなたはBroadwellにいるので、SkylakeのハードウェアのP-state機能はなく、最大のターボまで高速で立ち上げることができます。 36msは、(コアクロックサイクルではなく)ウォールクロック時間を測定するのに非常に短い。また、AVXのウォームアップ期間は約14時間です。ここで256b命令はおそらく4倍遅くなります:http://www.agner.org/optimize/blog/read.php?i=415。 AgnerはSkylakeの前でそれを観察しなかったと言いますが、他の人は持っています。とにかく、最初にウォーミングアップして制御しない限り、最初にSSEを実行すると、低クロック速度で時間がかかることがあります。 –

答えて

3

問題は、トンでありますあなたのデータはL1キャッシュに収まらない。 BroadwellのL1帯域幅はL2帯域幅よりもはるかに大きくなります。 L1帯域幅は、CPUサイクルごとに2つの32バイト・ベクトルをロードするのに十分な大きさです。したがって、あなたのデータセットがはるかに小さい場合、より良いAVX対SSEのスピードアップ が予想されるかもしれません。ただし、 のL1読み取り/書き込み帯域幅の合計は、1サイクルあたり2 * 32(r)+32(w)= 96バイト未満です。 実際には1サイクルあたり75バイトが可能です。hereを参照してください。

thisページ上の第2のグラフは、実際L2帯域幅が非常に小さいことを示している:Test_block_size = 128キロバイト(= 32キロバイトコアあたり)で 帯域幅は900ギガバイト/秒です。 Test_block_size = 1MB(=コアあたり256KB)の帯域幅はわずか300GB/sです。 (ハスウェル4770kはBroadwellマイクロアーキテクチャとほぼ同じL1およびL2キャッシュ・アーキテクチャを持っていることに注意してください。)

は、2000年にARを軽減し、1000000にNREPを高め、AVXのスピードアップ対SSEと何が起こるか見てみてください。

+2

gccのループはおそらくフロントエンドのボトルネックになっている可能性があります。その効果はAVX2に有利ですが、uopあたり2倍の作業を行うことが勝利だからです。 –

+0

@PeterCordesはい、それはここでも役割を果たすかもしれません。配列サイズ「AR」を2000に減らすと、予想通りAVXとSSEのスピードアップが2になりました。 – wim

関連する問題