AVX vs. SSE:期望看到更大的加速

时间:2017-11-04 20:50:16

标签: performance sse simd avx

我预计AVX比SSE快约1.5倍。所有3个阵列(3个阵列* 16384个元素* 4个字节/元素= 196608个字节)应该适合英特尔酷睿CPU(Broadwell)上的L2缓存(256KB)。

我应该使用任何特殊的编译器指令或标志吗?

编译器版本

$  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;
}

1 个答案:

答案 0 :(得分:3)

问题是您的数据不适合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。 (请注意,Haswell 4770k与Broadwell具有或多或少相同的L1和L2缓存架构。)

尝试将AR减少到2000并将NREP增加到1000000,看看SSE与AVX加速会发生什么。