2017-11-04 36 views
1

나는 AVX가 SSE보다 약 1.5 배 빠르다고 기대했다. Intel Core CPU (Broadwell)의 3 개 어레이 (3 배열 * 16384 요소 * 4 바이트/요소 = 196608 바이트)는 L2 캐시 (256KB)에 적합해야합니다.AVX 대 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 사이에서 순서를 바꾸어 놓으십시오. 결국에는 (skylake에서) 당신이 기대하는 1.5에 꽤 가까운 비율을 얻습니다. –

+2

요소 당 계산량이 아주 적기 때문에 (주로 추가) 실행 시간은 대부분 메모리 한계 일 것으로 예상됩니다. – EOF

+1

Broadwell을 사용 중이므로 최대 터보까지 빠른 상승 속도를 위해 Skylake의 하드웨어 P- 상태 기능이 없습니다. 36ms는 (코어 클록 사이클이 아닌) 벽시계 측정에 매우 짧습니다. 약 14 us의 AVX 워밍업 기간도 있습니다. 256b 명령어가 4 배 더 느릴 수도 있습니다 : http://www.agner.org/optimize/blog/read.php?i=415. Agner는 Skylake 전에서는 관찰하지 않았지만 다른 사람들은 Skylake에서 관찰했다. 어쨌든 SSE를 실행하려면 먼저 워밍업으로 제어하지 않는 한 저속 클럭 속도로 시간을 가져야합니다. –

답변

3

문제는 t이다 데이터가 L1 캐시에 맞지 않습니다. Broadwell의 L1 대역폭은 L2 대역폭보다 훨씬 큽니다. L1 대역폭은 CPU주기마다 두 개의 32 바이트 벡터를로드 할만큼 충분히 큽니다. 따라서 데이터 세트가 훨씬 작은 경우 더 나은 AVX 대 SSE 속도 향상 이 필요할 수 있습니다. 그러나 결합 된 L1 읽기/쓰기 대역폭은 사이클 당 2 * 32 (r) +32 (w) = 96 바이트보다 작습니다. 실제로 사이클 당 75 바이트가 가능합니다 (here 참조).

this 페이지의 두 번째 그래프는 실제로 L2 대역폭이 훨씬 더 작음을 보여줍니다. Test_block_size = 128KB (코어 당 32KB)에서 대역폭은 900GB/s입니다. Test_block_size = 1MB (= 코어 당 256KB) 대역폭은 300GB/s에 불과합니다. (참고 하 스웰 4770k가 가지고있는 더 많거나 적은 같은 L1 및 브로드 웰 등의 L2 캐시 아키텍처.) 2000 AR을 줄이고 1000000 NREP을 증가시키고 SSE AVX 대 속도 향상으로 어떻게되는지

보십시오.

+2

gcc의 루프가 프론트 엔드 병목 현상을 일으킨 것 같습니다. 그 효과는 AVX2에 유리하지만, uop 당 2 배의 작업을 수행하는 것이이기므로. –

+0

@PeterCordes 예, 여기에 중요한 역할을 할 수도 있습니다. 어레이 크기 'AR'을 2000으로 줄이면 예상대로 AVX 대 SSE 속도가 2가됩니다. – wim