2017-04-06 7 views
5

はるかに高速です。私が使用するテストコードは以下の通りです。 -O2でコンパイルすると1.7秒かかります。 -O2 -mavxでコンパイルすると、わずか1.0秒しかかかりません。 vexでエンコードされていないスカラー操作は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

システム:[email protected](Skylakeマイクロアーキテクチャ)は、32 GBのMEMは、Ubuntu 16.10、GCC 6.3

テストコード

//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のモバイルハースウェルCPUで、両方とも約0.8秒を得て、clangでコンパイルします。 –

+0

@PaulR、ありがとう。後で私のHaswellシステムでテストすることができます。私はSkylakeシステム上で私がハズウェルに乗っていないという奇妙な結果を得ているので、私は驚かないだろう。 –

+0

@PaulR、私はそれを考え出した!'omp_get_wtime()'を呼び出した直後に '__asm__ __volatile__(" vzeroupper ":::);'を返します。 –

答えて

5

問題が関連します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との最初の呼び出しは、しかし、クラン(クランも-O2でベクトル化以来clang -O2 -fno-vectorizeを使用)と、それが唯一のclock_gettimeへのすべての呼び出し後にそれを使用して消えた後、GCCで

問題が//__asm__ __volatile__ ("vzeroupper" : : :);を使用して消えます。ここで

-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との)私はこれをテストするために使用するコードですclock_gettimeへの最初の呼び出しは、GCCと似ています。

私は-z nowと私は右main()__asm__ __volatile__ ("vzeroupper" : : :);のみを必要とするだろうが、私はまだclock_gettimeの最初の呼び出しの後にそれを必要とすることを期待。

+1

ニースコード! [このgcc webpage](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'はコメントされていてはいけません。それは、私がGCCをテストするときに、すべての呼び出しの代わりに 'vzeroupperonce'を呼び出す必要があるためです。私は '__builtin_cpu_init'について知らなかった。私はAVXをテストしていないシステムはありませんが、それがなくても機能しました。私は安全のために答えに加えました。 –

+0

'_dl_runtime_resolve_avx'は、最初の呼び出し時にのみ** **別の共有ライブラリファイルの関数に呼び出されます。 lazyバインディングを無効にしてみてください(http://man7.org/linux/man-pages/man1/ld.1.html - "lazy ..関数が呼び出される時点まで関数呼び出しの解決を延期するように動的リンカーに指示します(遅延呼び出しバインドされたものではなく、読み込み時よりも遅いバインドがデフォルトです) ")をエクスポートLD_BIND_NOW = 1(http://man7.org/linux/man-pages/man8/ld.so.8.html - 実行時に '_dl_runtime_resolve_avx'の呼び出しを無効にするために、" deferring "ではなくプログラム起動時にすべてのシンボルを解決する)。 – osgx

関連する問題

 関連する問題