Les opérations scalaires AVX sont beaucoup plus rapides

Je teste la fonction simple suivante

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

avec de très grands tableaux de sorte qu’il est lié à la bande passante de la mémoire. Le code de test que j’utilise est ci-dessous. Lorsque je comstack avec -O2 cela prend 1,7 seconde. Lorsque je comstack avec -O2 -mavx cela ne prend que 1,0 seconde. Les opérations scalaires non encodées en vex sont 70% plus lentes! Pourquoi est-ce?

Voici l’assemblage pour -O2 et -O2 -mavx . <img src="http://soffr.miximages.com/c/otliN.png" alt="vimddif de -O2 et de -O2 -mavx “>

https://godbolt.org/g/w4p60f

Système: i7-6700HQ@2.60GHz (Skylake) 32 Go de mémoire, Ubuntu 16.10, GCC 6.3

Code de test

 //gcc -O2 -fopenmp test.c //or //gcc -O2 -mavx -fopenmp test.c #include  #include  #include  #include  #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); } 

Le problème est lié à la moitié supérieure sale d’un registre AVX après l’appel de omp_get_wtime() . C’est un problème en particulier pour les processeurs Skylake.

La première fois que j’ai lu sur ce problème était ici . Depuis lors, d’autres personnes ont observé ce problème: ici et ici .

À l’aide de gdb j’ai constaté que omp_get_wtime() appelle clock_gettime . J’ai réécrit mon code pour utiliser clock_gettime() et je vois le même problème.

 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 } 

En parcourant le code avec gdb je vois que la première fois que clock_gettime est appelé, il appelle _dl_runtime_resolve_avx() . Je crois que le problème est dans cette fonction basée sur ce commentaire . Cette fonction ne semble être appelée que la première fois que clock_gettime est appelé.

Avec GCC, le problème disparaît avec //__asm__ __volatile__ ( "vzeroupper" : : : ); après le premier appel avec clock_gettime cependant avec Clang (avec clang -O2 -fno-vectorize puisque Clang vectorise même à -O2 ), il ne l’utilise qu’après chaque appel à clock_gettime .

Voici le code que j’ai utilisé pour tester cela (avec GCC 6.3 et Clang 3.8)

 #include  #include  #include  #include  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 

Si je désactive -z now résolution d'appel de fonction paresseuse avec -z now (par exemple, clang -O2 -fno-vectorize -z now foo.c ), alors Clang n'a besoin que de __asm__ __volatile__ ( "vzeroupper" : : : ); après le premier appel à clock_gettime tout comme GCC.

Je m'attendais à ce que, avec -z now je n’ai plus besoin que de __asm__ __volatile__ ( "vzeroupper" : : : ); juste après main() mais j'en ai toujours besoin après le premier appel à clock_gettime .