2016-09-12 2 views
0

Je souhaite mesurer la différence de temps entre l'accès à une entrée de table et l'accès à une autre entrée après un clflush. Ci-dessous vous trouverez ma tentative, je ne reçois presque aucune pénalité pour les deux opérations ci-dessus. La table est de longueur 256 avec 8 bits dans chaque entrée. Je soupçonne que mon clflush ne fonctionne pas correctement. Je compile avec le drapeau -O3 dans gcc.Comment utiliser clflush?

  #include <stdio.h> 
      #include <stdlib.h> 
      #include <stdint.h> 
      #define ARRAYSIZE(arr) (sizeof(arr)/sizeof(arr[0])) 

      #define REPEAT 10000 

      unsigned char table[256]={103,198,105,115,81,255,74,236,41,205,186,171,242,251,227,70,124,194,84,248,27,232,231,141,118,90,46,99,51,159,201,154,102,50,13,183,49,88,163,90,37,93,5,23,88,233,94,212,171,178,205,198,155,180,84,17,14,130,116,65,33,61,220,135,112,233,62,161,65,225,252,103,62,1,126,151,234,220,107,150,143,56,92,42,236,176,59,251,50,175,60,84,236,24,219,92,2,26,254,67,251,250,170,58,251,41,209,230,5,60,124,148,117,216,190,97,137,249,92,187,168,153,15,149,177,235,241,179,5,239,247,0,233,161,58,229,202,11,203,208,72,71,100,189,31,35,30,168,28,123,100,197,20,115,90,197,94,75,121,99,59,112,100,36,17,158,9,220,170,212,172,242,27,16,175,59,51,205,227,80,72,71,21,92,187,111,34,25,186,155,125,245,11,225,26,28,127,35,248,41,248,164,27,19,181,202,78,232,152,50,56,224,121,77,61,52,188,95,78,119,250,203,108,5,172,134,33,43,170,26,85,162,190,112,181,115,59,4,92,211,54,148,179,175,226,240,228,158,79,50,21,73,253,130,78,169}; 



      inline void clflush(volatile void *p) 
      { 
       asm volatile ("clflush (%0)" :: "r"(p)); 
      } 

      inline uint64_t rdtsc() 
      { 
       unsigned long a, d; 
       asm volatile ("cpuid; rdtsc" : "=a" (a), "=d" (d) : : "ebx", "ecx"); 
       return a | ((uint64_t)d << 32); 
      } 

      inline int func(int *a) { 
       int i; 
       for(i=0;i<REPEAT;i++){ 
        a[i]=(int)table[rand()%256]; 
       } 

      } 
      void flushCache(unsigned char *start) 
      { 
       // flush table 
       unsigned char* fPtr = (unsigned char*)start; 
       clflush(fPtr); 
       clflush(fPtr+64); 
       clflush(fPtr+128); 
       clflush(fPtr+192); 
       clflush(fPtr+256); 
      } 


      inline void test() 
      { 
       int i=0; 
       uint64_t start, end; 
       char c; 
       int temp[REPEAT]; 

       start = rdtsc(); 

       func(temp); 

       end = rdtsc(); 

       //following line of code to prevent compiler from optimizing. do something with the return value 
       for(i-0;i<REPEAT;i++){ 
       temp[i]=temp[i]+temp[i/2]; 
       } 

       printf("%ld ticks\n", end - start); 
      } 

      inline void testflush() 
      { 
       int i=0; 
       uint64_t start, end; 
       char c; 
       int temp[REPEAT]; 

       start = rdtsc(); 

       func(temp); 
       flushCache(table); //flush afer every read 

       end = rdtsc(); 

       //following line of code to prevent compiler from optimizing. do something with the return value 
       for(i-0;i<REPEAT;i++){ 
       temp[i]=temp[i]+temp[i/2]; 
       } 

       printf("%ld ticks\n", end - start); 
      } 



      int main(int ac, char **av) 
      { 
       test(); 
       printf("Tables in cache!\n"); 
       testflush(); 
       printf("Tables evicted from cache.\n"); 
       test(); 

       return 0; 
      } 

mise à jour: Je comprends qu'il pourrait y avoir un problème dû à l'accès à la table. Voici un autre code qui expulse une seule variable au lieu de la table entière. Celui-ci montre une augmentation significative du cycle d'horloge lors de l'utilisation du clflush(). Cela signifie-t-il que clflush() fonctionne correctement et que le temps incrémenté est dû à l'accès à la variable depuis la mémoire?

  #include <stdint.h> 
      #include <stdio.h> 
      #define REPEAT 100000 
      inline void clflush(volatile void *p) 
      { 
       asm volatile ("clflush (%0)" :: "r"(p)); 
      } 

      inline uint64_t rdtsc() 
      { 
       unsigned long a, d; 
       asm volatile ("rdtsc" : "=a" (a), "=d" (d)); 
       return a | ((uint64_t)d << 32); 
      } 

      volatile int i; 

      inline void test() 
      { 
       uint64_t start, end,clock; 
       volatile int j; 
       long int rep; 
       int k; 

       clock=0; 
       for(rep=0;rep<REPEAT;rep++){ 
        start = rdtsc(); 
        j = i+1; 
        end = rdtsc(); 
        clock=clock+(end-start); 
        k=j; 
       } 
       printf("took %lu ticks\n", clock); 
      } 

      inline void testflush() 
      { 
       uint64_t start, end,clock; 
       volatile int j; 
       int k; 
       long int rep; 

       clock=0; 
       for(rep=0;rep<REPEAT;rep++){ 
        start = rdtsc(); 
        j = i+1; 
        end = rdtsc(); 
        clflush(&i); 
        clock=clock+(end-start); 
        k=j; 
       } 
       printf("took %lu ticks\n", clock); 
      } 


      int main(int ac, char **av) 
      { 
       i=5; 
       printf("------------------------------------------\n"); 
       test(); 
       printf("------------------------------------------\n"); 
       testflush(); 
       printf("------------------------------------------\n"); 
       test(); 
       return 0; 
      } 
+0

'clflush (fPtr + 256);'? Note: il n'y a pas de raison pour 'fPtr'. – Olaf

+0

ok. Alors comment dois-je le faire? – Rick

+0

@ user2764478: Oui, (pour la 2ème version) une augmentation significative des cycles impliquerait que 'clflush' fonctionne. – Brendan

Répondre

0

Quelques problèmes que je vois avec le code.

Vous terminez la temporisation pour testflush après avoir appelé clflush. Par conséquent, vous synchronisez les cycles nécessaires pour le traitement de ces instructions. Je ne pense pas que ce soit voulu.

Dans votre fonction de test, vous avez une boucle avec 10000 itérations. Chaque itération peut invoquer une référence à une nouvelle ligne de cache, mais il n'y a que 4 lignes de cache dans table. Donc, au moins 9996 itérations n'invoquent aucun cache manquant de toute façon.

Ainsi vous synchronisez 10000 fois rand()%256 plus 4 charges de cache. Même si les charges de cache prennent quelques centaines de cycles, 10000 itérations de rand()%256 l'éclipseront toujours.

Ces 10000 entiers générés doivent également être réécrits. Je ne suis pas sûr que la bande passante du cache L1-> L2 soit un facteur limitant, mais ce pourrait être le cas.

Vous devez également exécuter le test quelques milliers de fois ou plus et la moyenne, la variance de l'échantillon est beaucoup trop élevé sinon.

Ensuite, il peut également être possible que le CPU prédit à nouveau les lignes de cache par spéculation avant de les demander. C'est permis de le faire, mais je ne sais pas à quel point les cpus sont intelligents.

+0

Merci pour votre aide. J'ai un autre ajouté un autre code, qui ne mesure pas le temps pour clflush(). Ce code montre une diminution significative de l'efficacité lors de l'utilisation de clflush() (de presque un facteur 4). Pouvez-vous s'il vous plaît vérifier si cette diminution de l'efficacité se produit en raison de rinçage? – Rick

+0

@ user2764478 Votre code mis à jour prend environ 50% de temps en plus avec clflush sur mon système. Les instructions de rdtsc semblent prendre autant de temps que le cache manqué. Cependant, vous avez supprimé l'instruction cpuid, ce qui signifie que le nombre de ticks sera moins fiable. L'ajout du cache cache est complètement occulté à nouveau. – user4407569

+0

ligne uint64_t rdtsc() \t \t \t \t { \t \t \t \t unsigned long a, d; Asm volatile ("cpuid; rdtsc": "= a" (a), "= d" (d):: "ebx", "ecx"); \t \t \t \t \t \t \t \t retour a | ((uint64_t) d << 32); \t \t \t \t} – Rick