2017-04-06 13 views
5

훨씬 빠릅니다. 내가 사용하는 테스트 코드는 다음과 같습니다. -O2으로 컴파일하면 1.7 초가 걸립니다. -O2 -mavx으로 컴파일하면 1.0 초 밖에 걸리지 않습니다. 짜증나게 인코딩되지 않은 스칼라 작업은 70 % 더 느립니다! 왜 그런가요?AVX 스칼라 연산은 결합 된 메모리 대역폭이되도록 내가 매우 큰 배열을 다음과 같은 간단한 기능을</p> <pre><code>void mul(double *a, double *b) { for (int i = 0; i<N; i++) a[i] *= b[i]; } </code></pre> <p>을 테스트

다음은 -O2-O2 -mavx의 어셈블리입니다. vimddif of <code>-O2</code> and <code>-O2 -mavx</code>

https://godbolt.org/g/w4p60f

시스템 : 문제가 관련되어

//gcc -O2 -fopenmp test.c 
//or 
//gcc -O2 -mavx -fopenmp test.c 
#include <string.h> 
#include <stdio.h> 
#include <x86intrin.h> 
#include <omp.h> 

#define N 1000000 
#define R 1000 

void mul(double *a, double *b) { 
    for (int i = 0; i<N; i++) a[i] *= b[i]; 
} 

int main() { 
    double *a = (double*)_mm_malloc(sizeof *a * N, 32); 
    double *b = (double*)_mm_malloc(sizeof *b * N, 32); 

    //b must be initialized to get the correct bandwidth!!! 
    memset(a, 1, sizeof *a * N); 
    memset(b, 1, sizeof *b * N); 

    double dtime; 
    const double mem = 3*sizeof(double)*N*R/1024/1024/1024; 
    const double maxbw = 34.1; 
    dtime = -omp_get_wtime(); 
    for(int i=0; i<R; i++) mul(a,b); 
    dtime += omp_get_wtime(); 
    printf("time %.2f s, %.1f GB/s, efficency %.1f%%\n", dtime, mem/dtime, 100*mem/dtime/maxbw); 

    _mm_free(a), _mm_free(b); 
} 
+0

FWIW 저조한 2.6GHz 모바일 Haswell CPU에서 둘 다 약 0.8 초를 얻습니다. clang으로 컴파일합니다. –

+0

@PaulR, 확인해 주셔서 감사합니다. 나중에 Haswell 시스템에서 테스트 할 수 있습니다. 내 Skylake 시스템에서 이상한 결과를 얻고 있는데, 나는 Haswell에 도착하지 않아서 놀라지 않을 것입니다. –

+0

@PaulR, 방금 알아 냈습니다!'omp_get_wtime()'을 호출 한 직후에'__asm__ __volatile__ ("vzeroupper": : :); –

답변

5

[email protected] (스카이 레이크) 32기가바이트의 MEM 우분투 16.10, GCC 6.3

테스트 코드 omp_get_wtime()을 호출 한 후 AVX 레지스터의 더러운 상반 분. 특히 Skylake 프로세서의 경우 문제입니다.

이 문제에 대해 처음 읽은 것은 here입니다. 그 이후로 다른 사람들이이 문제를 관찰했습니다 : herehere.

gdb을 사용하면 omp_get_wtime()clock_gettime입니다. clock_gettime()을 사용하기 위해 코드를 다시 작성했는데 같은 문제가 발생합니다.

void fix_avx() { __asm__ __volatile__ ("vzeroupper" : : :); } 
void fix_sse() { } 
void (*fix)(); 

double get_wtime() { 
    struct timespec time; 
    clock_gettime(CLOCK_MONOTONIC, &time); 
    #ifndef __AVX__ 
    fix(); 
    #endif 
    return time.tv_sec + 1E-9*time.tv_nsec; 
} 

void dispatch() { 
    fix = fix_sse; 
    #if defined(__INTEL_COMPILER) 
    if (_may_i_use_cpu_feature (_FEATURE_AVX)) fix = fix_avx; 
    #else 
    #if defined(__GNUC__) && !defined(__clang__) 
    __builtin_cpu_init(); 
    #endif 
    if(__builtin_cpu_supports("avx")) fix = fix_avx; 
    #endif 
} 

gdb와 코드를 단계별로 나는 처음 clock_gettime 그것이 _dl_runtime_resolve_avx()를 호출이라고 것을 알 수있다. 문제는 this comment을 기반으로 한이 기능에 있다고 생각합니다. 이 함수는 처음으로 clock_gettime이 호출 될 때만 나타납니다. 문제가 해결 그러나 연타와 clock_gettime와 첫 번째 호출 후 //__asm__ __volatile__ ("vzeroupper" : : :);를 사용하여 간다 GCC으로

단지 clock_gettime 호출 할 때마다 후를 사용하여 도망 간다 (연타도 -O2에서 벡터화 이후 clang -O2 -fno-vectorize 사용). 여기

-z now (예 : clang -O2 -fno-vectorize -z now foo.c는) 다음 연타가 후에 만 ​​ __asm__ __volatile__ ("vzeroupper" : : :);을 필요로

#include <string.h> 
#include <stdio.h> 
#include <x86intrin.h> 
#include <time.h> 

void fix_avx() { __asm__ __volatile__ ("vzeroupper" : : :); } 
void fix_sse() { } 
void (*fix)(); 

double get_wtime() { 
    struct timespec time; 
    clock_gettime(CLOCK_MONOTONIC, &time); 
    #ifndef __AVX__ 
    fix(); 
    #endif 
    return time.tv_sec + 1E-9*time.tv_nsec; 
} 

void dispatch() { 
    fix = fix_sse; 
    #if defined(__INTEL_COMPILER) 
    if (_may_i_use_cpu_feature (_FEATURE_AVX)) fix = fix_avx; 
    #else 
    #if defined(__GNUC__) && !defined(__clang__) 
    __builtin_cpu_init(); 
    #endif 
    if(__builtin_cpu_supports("avx")) fix = fix_avx; 
    #endif 
} 

#define N 1000000 
#define R 1000 

void mul(double *a, double *b) { 
    for (int i = 0; i<N; i++) a[i] *= b[i]; 
} 

int main() { 
    dispatch(); 
    const double mem = 3*sizeof(double)*N*R/1024/1024/1024; 
    const double maxbw = 34.1; 

    double *a = (double*)_mm_malloc(sizeof *a * N, 32); 
    double *b = (double*)_mm_malloc(sizeof *b * N, 32); 

    //b must be initialized to get the correct bandwidth!!! 
    memset(a, 1, sizeof *a * N); 
    memset(b, 1, sizeof *b * N); 

    double dtime; 
    //dtime = get_wtime(); // call once to fix GCC 
    //printf("%f\n", dtime); 
    //fix = fix_sse; 

    dtime = -get_wtime(); 
    for(int i=0; i<R; i++) mul(a,b); 
    dtime += get_wtime(); 
    printf("time %.2f s, %.1f GB/s, efficency %.1f%%\n", dtime, mem/dtime, 100*mem/dtime/maxbw); 

    _mm_free(a), _mm_free(b); 
} 

내가 게으른 함수 호출 해상도를 사용하지 않도록 설정하면 (GCC 6.3와 연타 3.8) 나는 이것을 테스트하는 데 사용되는 코드입니다 첫 번째 호출은 GCC처럼 clock_gettime입니다.

내가 -z now과 같이 나타납니다. __asm__ __volatile__ ("vzeroupper" : : :);main() 바로 뒤에 필요하지만, 여전히 clock_gettime을 처음 호출 한 후에도 필요합니다.

+1

좋은 코드! [이 gcc 웹 페이지] (https://gcc.gnu.org/onlinedocs/gcc/x86-Built-in-Functions.html)에서 호출하기 전에'__builtin_cpu_init (void)'를 호출해야하는지 여부는 분명하지 않았습니다. '__builtin_cpu_supports ("avx")'또는 아닙니다. 낡은 비 AVX CPU에서 코드를 테스트 했습니까? – wim

+1

@wim,'dispatch'는 주석 처리되어서는 안됩니다. 그 이유는 모든 호출 대신'vzeroupperonce '를 호출 할 필요가있는 GCC를 테스트했기 때문입니다. 나는'__builtin_cpu_init'에 대해 몰랐다. AVX가없는 시스템이 없어도 작동했습니다. 나는 안전을 위해서 대답에 그것을 덧붙였다. –

+0

'_dl_runtime_resolve_avx'는 첫 번째 호출 **에서 ** 다른 공유 라이브러리 파일의 일부 함수에만 호출됩니다. lazy 바인딩을 비활성화하십시오 (http://man7.org/linux/manage-man1/ld.1.html - "lazy .. 동적 링커에게 함수 호출이 지연 될 때까지 함수 호출 해결을 지연하도록 알려줍니다 (lazy binding)을 사용하는 것이 아니라,'export LD_BIND_NOW = 1' (http://man7.org/linux/man-pages/man8/ld.so.8.html - "load LD_BIND_NOW = 런타임에'_dl_runtime_resolve_avx' 호출을 사용하지 못하도록 연기하는 대신 프로그램 시작시 모든 심볼을 확인하십시오). – osgx