2016-07-04 2 views
0

tous Hy,Test - Odd résultats

me demandais l'autre jour comment les modèles d'accès très différentes affecté la vitesse de lecture mémoire (pensée principalement sur la fréquence vs discussion de la taille du bus, et l'impact du taux de succès de cache) , donc fait un petit programme pour tester la vitesse de la mémoire en faisant des accès séquentiels et entièrement aléatoires, mais les résultats que j'ai obtenus sont assez bizarres, donc je ne fais pas confiance à mon code.

Mon idée était assez simple, juste en boucle sur un tableau et déplacer les données dans un registre. Fait 3 versions, on déplace 128 bits à la fois avec sse, l'autre 32, et le dernier 32 encore mais en faisant deux movs, le premier chargeant un nombre aléatoire d'un tableau, et le deuxième lisant à partir de la position spécifiée par la valeur prev. J'ai obtenu ~ 40 Go/s pour la version sse, que c'est raisonnable considérant que j'utilise un i7 4790K avec DDR3 1600 cl9 mémoire à double canal, qui donne environ 25 Go/s, alors ajoutez à ce cache et ça me fait plaisir, mais alors j'ai 3,3 Go/s pour le séquentiel normal, et le pire, 15 Go/s pour le hasard. Ce dernier résultat me fait penser que le banc est bidon.

Ci-dessous est le code, si quelqu'un pouvait faire la lumière sur ce qu'il serait apprécié. Est-ce que la boucle interne dans l'assemblage pour s'assurer qu'il a seulement fait un mov.

EDIT: Nous avons réussi à obtenir un peu plus de performance en utilisant vlddqu ymm0, buffL [esi] (AVX) au lieu de movlps, est passé de 38 Go/s à 41 Go/s

EDIT 2: Est-ce un peu plus tester, dérouler la boucle de l'assemblage interne, faire une version qui charge 4 fois par itération et une autre qui charge 8 fois. Obtenu ~ 35 GB/s pour la version x4 et ~ 24 GB/s pour la version x8

#define PASSES 1000000 

double bw = 0; 

int main() 
{ 
    cout << "Running : "; 

    bw = 0; 
    for(int n = 0; n < PASSES;n++) 
    { 
     if(n % 100000 == 0) cout << "."; 
     const int l = 1 << 16; 
     int buffL[l]; 

     LARGE_INTEGER frequency;  // ticks per second 
     LARGE_INTEGER t1, t2;   // ticks 

     // get ticks per second 
     QueryPerformanceFrequency(&frequency); 

     // start timer 
     QueryPerformanceCounter(&t1); 

     int maxByte = l*4; 
     __asm 
     { 
      push esi 

      mov esi,0 

      loopL0: 
       movlps xmm0, buffL[esi] 
       add esi,16 
       cmp esi,maxByte 
       jb loopL0 

      pop esi 
     } 

     // stop timer 
     QueryPerformanceCounter(&t2); 

     // compute elapsed time in millisec 
     double ms = (t2.QuadPart - t1.QuadPart) * 1000.0/frequency.QuadPart; 

     bw += (double(4ull*l)/1073741824.0)/(double(ms)*0.001); 
    } 
    bw /= double(PASSES); 

    cout << endl; 
    cout << " Sequential (SSE) : " << bw << " GB/s " << endl; 

    cout << "Running : "; 
    bw = 0; 
    for(int n = 0; n < PASSES;n++) 
    { 
     if(n % 100000 == 0) cout << "."; 
     const int l = 1 << 16; 
     int buffL[l]; 

     for(int t = 0;t < l;t++) buffL[t] = (t+1)*4; 

     LARGE_INTEGER frequency;  // ticks per second 
     LARGE_INTEGER t1, t2;   // ticks 

     // get ticks per second 
     QueryPerformanceFrequency(&frequency); 

     // start timer 
     QueryPerformanceCounter(&t1); 

     int maxByte = l*4; 
     __asm 
     { 
      push esi 

      mov esi,0 

      loopL1: 
       mov esi, buffL[esi] 
       cmp esi,maxByte 
       jb loopL1 

      pop esi 
     } 

     // stop timer 
     QueryPerformanceCounter(&t2); 

     // compute elapsed time in millisec 
     double ms = (t2.QuadPart - t1.QuadPart) * 1000.0/frequency.QuadPart; 

     bw += (double(4ull*l)/1073741824.0)/(double(ms)*0.001); 
    } 
    bw /= double(PASSES); 

    cout << endl; 
    cout << " Sequential : " << bw << " GB/s " << endl; 

    cout << "Running : "; 

    bw = 0; 
    for(int n = 0; n < PASSES;n++) 
    { 
     if(n % 100000 == 0) cout << "."; 
     const int l = 1 << 14; 
     int buffL[l]; 

     int maxByte = l*4; 

     int roffset[l]; 
     for(int t = 0;t < l;t++) roffset[t] = (rand()*4) % maxByte; 

     LARGE_INTEGER frequency;  // ticks per second 
     LARGE_INTEGER t1, t2;   // ticks 

     // get ticks per second 
     QueryPerformanceFrequency(&frequency); 

     // start timer 
     QueryPerformanceCounter(&t1); 

     __asm 
     { 
      push esi 
      push edi 

      mov esi,0 

      loopL2: 
       mov edi, roffset[esi] 
       mov edi, buffL[edi] 
       add esi,4 
       cmp esi,maxByte 
       jb loopL2 

      pop edi 
      pop esi 
     } 

     // stop timer 
     QueryPerformanceCounter(&t2); 

     // compute elapsed time in millisec 
     double ms = (t2.QuadPart - t1.QuadPart) * 1000.0/frequency.QuadPart; 

     bw += (double(2*4ull*l)/1073741824.0)/(double(ms)*0.001); 
    } 
    bw /= double(PASSES); 

    cout << endl; 
    cout << " Random : " << bw << " GB/s " << endl; 

    return 0; 
} 
+0

Pour éviter la possibilité d'un essai influer sur l'issue d'une autre en raison de l'une optimisation du compilateur ou le cache, séparer chacun de vos trois tests boucles dans leur propre fichier source. (Pour que chacun soit compilé séparément). Puis un autre fichier source qui implémente "main" et prend un argument de la ligne de commande dont test à exécuter. Exécutez les tests séparément et exécutez plusieurs fois. – selbie

+0

Déclarez maxByte comme const. 'const int maxByte = l * 4;' – selbie

Répondre

0

Votre second test a une matrice sur la pile qui est 1 << 16 en taille. C'est 64k. Ou plus facile à lire:

int buffL[65536]; 

Votre troisième test a deux matrices sur la pile. Les deux à 1 < < 14 'en taille. C'est 16K chaque

int buffL[16384]; 
int roffset[16384]; 

donc tout de suite que vous utilisez une taille de pile beaucoup plus petit (à savoir moins de pages mises en cache et étant permutées). Je pense que votre boucle n'effectue que l'itération deux fois moins de fois dans le troisième test que dans le second. Peut-être que vous vouliez déclarer 1 << 15 (ou 1 << 16) comme taille à la place pour chaque tableau à la place?

1

Rassembler le code de mesure dans une classe de la bande passante, ce qui crée des constantes, ayant les trois tests utilisent le même tampon (et la taille) aligner les sommets des boucles et le calcul de décalage aléatoire dans l'ensemble du tampon (3e essai):

#include "stdafx.h" 
#include "windows.h" 
#include <iostream> 
#include <vector> 

using namespace std; 

constexpr size_t passes = 1000000; 
constexpr size_t buffsize = 64 * 1024; 
constexpr double gigabyte = 1024.0 * 1024.0 * 1024.0; 
constexpr double gb_per_test = double(long long(buffsize) * passes)/gigabyte; 

struct Bandwidth 
{ 
    LARGE_INTEGER pc_tick_per_sec; 
    LARGE_INTEGER start_pc; 
    const char* _label; 
public: 
    Bandwidth(const char* label): _label(label) 
    { 
     cout << "Running : "; 
     QueryPerformanceFrequency(&pc_tick_per_sec); 
     QueryPerformanceCounter(&start_pc); 
    } 

    ~Bandwidth() { 
     LARGE_INTEGER end_pc{};   
     QueryPerformanceCounter(&end_pc); 
     const auto seconds = double(end_pc.QuadPart - start_pc.QuadPart)/pc_tick_per_sec.QuadPart; 
     cout << "\n " << _label << ": " << gb_per_test/seconds << " GB/s " << endl; 
    } 
}; 

int wmain() 
{ 
    vector<char> buff(buffsize, 0); 
    const auto buff_begin = buff.data(); 
    const auto buff_end = buff.data()+buffsize; 

    { 
     Bandwidth b("Sequential (SSE)"); 

     for (size_t n = 0; n < passes; ++n) { 
      __asm { 
       push esi 
       push edi 
       mov esi, buff_begin 
       mov edi, buff_end 

       align 16 
      loopL0: 
       movlps xmm0, [esi] 
       lea esi, [esi + 16] 
       cmp esi, edi 
       jne loopL0 

       pop edi 
       pop esi 
      } 
     } 
    } 

    { 
     Bandwidth b("Sequential (DWORD)"); 
     for (int n = 0; n < passes; ++n) { 
      __asm { 
       push esi 
       push edi 
       mov esi, buff 
       mov edi, buff_end 
       align 16 
       loopL1: 
       mov eax, [esi] 
       lea esi, [esi + 4] 
       cmp esi, edi 
       jne loopL1 

       pop edi 
       pop esi 
      } 
     } 
    } 

    { 
     uint32_t* roffset[buffsize]; 
     for (auto& roff : roffset) 
      roff = (uint32_t*)(buff.data())+(uint32_t)(double(rand())/RAND_MAX * (buffsize/sizeof(int))); 
     const auto roffset_end = end(roffset); 
     Bandwidth b("Random"); 

     for (int n = 0; n < passes; ++n) { 
      __asm { 
       push esi 
       push edi 
       push ebx 
       lea edi, roffset  //begin(roffset) 
       mov ebx, roffset_end //end(roffset) 

       align 16 
       loopL2: 
       mov esi, [edi] //fetch the next random offset 
       mov eax, [esi] //read from the random location 
       lea edi, [edi + 4] // point to the next random offset 
       cmp edi, ebx //are we done? 
       jne loopL2 

       pop ebx 
       pop edi 
       pop esi 
      } 
     } 
    } 
} 

J'ai aussi trouvé des résultats plus cohérents si je SetPriorityClass(GetCurrentProcess, HIGH_PRIORITY_CLASS); et SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);