44 votes

L'utilisation d'AVX intrinsics au lieu de SSE n'améliore pas la vitesse - pourquoi?

J'ai été en utilisant Intel SSE intrinsèques pour un certain temps avec de bons gains de performance. Donc, je m'attendais à la AVX intrinsèques pour plus de speed-up de mes programmes. Cela, malheureusement, n'a pas été le cas jusqu'à présent. Probablement que je suis en train de faire une erreur stupide, donc je serais très reconnaissant si quelqu'un pouvait m'aider.

J'utilise Ubuntu 11.10 avec g++ 4.6.1. J'ai compilé mon programme (voir ci-dessous)

g++ simpleExample.cpp -O3 -march=native -o simpleExample

Le système de test a un Intel core i7-2600 CPU.

Voici le code qui illustre mon problème. Sur mon système, je reçois la sortie

98.715 ms, b[42] = 0.900038 // Naive
24.457 ms, b[42] = 0.900038 // SSE
24.646 ms, b[42] = 0.900038 // AVX

Notez que le calcul sqrt(sqrt(sqrt(x))) a été choisi afin de s'assurer que la mémoire de la bande passante ne limite pas la vitesse d'exécution; c'est juste un exemple.

simpleExample.cpp:

#include <immintrin.h>
#include <iostream>
#include <math.h> 
#include <sys/time.h>

using namespace std;

// -----------------------------------------------------------------------------
// This function returns the current time, expressed as seconds since the Epoch
// -----------------------------------------------------------------------------
double getCurrentTime(){
  struct timeval curr;
  struct timezone tz;
  gettimeofday(&curr, &tz);
  double tmp = static_cast<double>(curr.tv_sec) * static_cast<double>(1000000)
             + static_cast<double>(curr.tv_usec);
  return tmp*1e-6;
}

// -----------------------------------------------------------------------------
// Main routine
// -----------------------------------------------------------------------------
int main() {

  srand48(0);            // seed PRNG
  double e,s;            // timestamp variables
  float *a, *b;          // data pointers
  float *pA,*pB;         // work pointer
  __m128 rA,rB;          // variables for SSE
  __m256 rA_AVX, rB_AVX; // variables for AVX

  // define vector size 
  const int vector_size = 10000000;

  // allocate memory 
  a = (float*) _mm_malloc (vector_size*sizeof(float),32);
  b = (float*) _mm_malloc (vector_size*sizeof(float),32);

  // initialize vectors //
  for(int i=0;i<vector_size;i++) {
    a[i]=fabs(drand48());
    b[i]=0.0f;
  }

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Naive implementation
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  s = getCurrentTime();
  for (int i=0; i<vector_size; i++){
    b[i] = sqrtf(sqrtf(sqrtf(a[i])));
  }
  e = getCurrentTime();
  cout << (e-s)*1000 << " ms" << ", b[42] = " << b[42] << endl;

// -----------------------------------------------------------------------------
  for(int i=0;i<vector_size;i++) {
    b[i]=0.0f;
  }
// -----------------------------------------------------------------------------

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// SSE2 implementation
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  pA = a; pB = b;

  s = getCurrentTime();
  for (int i=0; i<vector_size; i+=4){
    rA   = _mm_load_ps(pA);
    rB   = _mm_sqrt_ps(_mm_sqrt_ps(_mm_sqrt_ps(rA)));
    _mm_store_ps(pB,rB);
    pA += 4;
    pB += 4;
  }
  e = getCurrentTime();
  cout << (e-s)*1000 << " ms" << ", b[42] = " << b[42] << endl;

// -----------------------------------------------------------------------------
  for(int i=0;i<vector_size;i++) {
    b[i]=0.0f;
  }
// -----------------------------------------------------------------------------

// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// AVX implementation
// +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  pA = a; pB = b;

  s = getCurrentTime();
  for (int i=0; i<vector_size; i+=8){
    rA_AVX   = _mm256_load_ps(pA);
    rB_AVX   = _mm256_sqrt_ps(_mm256_sqrt_ps(_mm256_sqrt_ps(rA_AVX)));
    _mm256_store_ps(pB,rB_AVX);
    pA += 8;
    pB += 8;
  }
  e = getCurrentTime();
  cout << (e-s)*1000 << " ms" << ", b[42] = " << b[42] << endl;

  _mm_free(a);
  _mm_free(b);

  return 0;
}

Toute aide est appréciée!

43voto

Norbert P. Points 1512

En effet, VSQRTPS (instruction AVX) nécessite exactement deux fois plus de cycles que SQRTPS (instruction SSE) sur un processeur Sandy Bridge. Voir le guide d’optimisation d’Agner Fog: tableaux d’instructions , page 88.

Des instructions telles que racine carrée et division ne bénéficient pas d’AVX. Par contre, les additions, les multiplications, etc.

10voto

Evgeny Kluev Points 16685

Si vous souhaitez augmenter les performances de la racine carrée, vous pouvez utiliser les formules VRSQRTPS et Newton-Raphson au lieu de VSQRTPS:

 x0 = vrsqrtps(a)
x1 = 0.5 * x0 * (3 - (a * x0) * x0)
 

VRSQRTPS lui-même ne profite pas d'AVX, mais d'autres calculs en bénéficient.

Utilisez-le si 23 bits de précision vous suffisent.

6voto

SoapBox Points 14183

Selon le matériel de votre processeur, les instructions AVX peuvent être émulées dans le matériel en tant qu'instructions SSE. Vous aurez besoin de rechercher le numéro de pièce de votre processeur pour obtenir des spécifications exactes, mais c'est l'une des principales différences entre les processeurs Intel bas et haut de gamme, le nombre d'unités d'exécution spécialisées par rapport à l'émulation matérielle.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X