2013-10-02 13 views
9

Voglio scrivere un programma per ottenere la dimensione della cache (L1, L2, L3). Conosco l'idea generale di ciò.Scrivere un programma per ottenere dimensioni e livelli della cache della CPU

  1. allocare una matrice grande
  2. parte di esso accesso di formato differente ogni volta.

Così ho scritto un piccolo programma. Ecco il mio codice:

#include <cstdio> 
#include <time.h> 
#include <sys/mman.h> 

const int KB = 1024; 
const int MB = 1024 * KB; 
const int data_size = 32 * MB; 
const int repeats = 64 * MB; 
const int steps = 8 * MB; 
const int times = 8; 

long long clock_time() { 
    struct timespec tp; 
    clock_gettime(CLOCK_REALTIME, &tp); 
    return (long long)(tp.tv_nsec + (long long)tp.tv_sec * 1000000000ll); 
} 

int main() { 
    // allocate memory and lock 
    void* map = mmap(NULL, (size_t)data_size, PROT_READ | PROT_WRITE, 
        MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); 
    if (map == MAP_FAILED) { 
     return 0; 
    } 
    int* data = (int*)map; 

    // write all to avoid paging on demand 
    for (int i = 0;i< data_size/sizeof(int);i++) { 
     data[i]++; 
    } 

    int steps[] = { 1*KB, 4*KB, 8*KB, 16*KB, 24 * KB, 32*KB, 64*KB, 128*KB, 
        128*KB*2, 128*KB*3, 512*KB, 1 * MB, 2 * MB, 3 * MB, 4 * MB, 
        5 * MB, 6 * MB, 7 * MB, 8 * MB, 9 * MB}; 
    for (int i = 0; i <= sizeof(steps)/sizeof(int) - 1; i++) { 
     double totalTime = 0;  
     for (int k = 0; k < times; k++) { 
      int size_mask = steps[i]/sizeof(int) - 1; 
      long long start = clock_time(); 
      for (int j = 0; j < repeats; j++) { 
       ++data[ (j * 16) & size_mask ]; 
      } 
      long long end = clock_time(); 
      totalTime += (end - start)/1000000000.0; 
     } 
     printf("%d time: %lf\n", steps[i]/KB, totalTime); 
    } 
    munmap(map, (size_t)data_size); 
    return 0; 
} 

Tuttavia, il risultato è così strano:

1 time: 1.989998 
4 time: 1.992945 
8 time: 1.997071 
16 time: 1.993442 
24 time: 1.994212 
32 time: 2.002103 
64 time: 1.959601 
128 time: 1.957994 
256 time: 1.975517 
384 time: 1.975143 
512 time: 2.209696 
1024 time: 2.437783 
2048 time: 7.006168 
3072 time: 5.306975 
4096 time: 5.943510 
5120 time: 2.396078 
6144 time: 4.404022 
7168 time: 4.900366 
8192 time: 8.998624 
9216 time: 6.574195 

mio CPU è Intel (R) core (TM) i3-2350M. Cache L1: 32K (per dati), L2 Cache 256K, L3 Cache 3072K. Sembra che non segua nessuna regola. Non riesco a ottenere informazioni sulla dimensione della cache o sul livello di cache da quello. Qualcuno potrebbe dare un aiuto? Grazie in anticipo.

Aggiornamento: Segui @Leeor consiglio, io uso j*64 invece di j*16. Nuovi risultati:

1 time: 1.996282 
4 time: 2.002579 
8 time: 2.002240 
16 time: 1.993198 
24 time: 1.995733 
32 time: 2.000463 
64 time: 1.968637 
128 time: 1.956138 
256 time: 1.978266 
384 time: 1.991912 
512 time: 2.192371 
1024 time: 2.262387 
2048 time: 3.019435 
3072 time: 2.359423 
4096 time: 5.874426 
5120 time: 2.324901 
6144 time: 4.135550 
7168 time: 3.851972 
8192 time: 7.417762 
9216 time: 2.272929 
10240 time: 3.441985 
11264 time: 3.094753 

Due picchi, 4096K e 8192K. Ancora strano

risposta

5

Non sono sicuro che questo sia l'unico problema qui, ma è sicuramente il più grande - il tuo codice attiverà molto rapidamente i prefetcher di flusso HW, facendoti quasi sempre colpire nelle latenze L1 o L2.

Maggiori dettagli possono essere trovati qui - http://software.intel.com/en-us/articles/optimizing-application-performance-on-intel-coret-microarchitecture-using-hardware-implemented-prefetchers

Per il vostro punto di riferimento Si dovrebbe o li disabilitare (tramite BIOS o qualsiasi altro mezzo), o almeno fare i passi più sostituendo j*16 (* 4 byte per int = 64B, una linea di cache - una classica unità di passo per il rilevatore di flusso), con j*64 (4 linee di cache). La ragione è che - il prefetcher può rilasciare 2 prefetches per richiesta di streaming, quindi va avanti rispetto al tuo codice quando fai passi di unità, può ancora ottenere un po 'più avanti quando il tuo codice sta saltando oltre 2 linee, ma diventa per lo più inutile con più tempo salti (3 non va bene a causa della tua modulu, hai bisogno di un divisore di step_size)

Aggiorna le domande con i nuovi risultati e possiamo capire se c'è qualcos'altro qui.


Edit1: Ok, ho eseguito il codice fisso e ottenuto -

1 time: 1.321001 
4 time: 1.321998 
8 time: 1.336288 
16 time: 1.324994 
24 time: 1.319742 
32 time: 1.330685 
64 time: 1.536644 
128 time: 1.536933 
256 time: 1.669329 
384 time: 1.592145 
512 time: 2.036315 
1024 time: 2.214269 
2048 time: 2.407584 
3072 time: 2.259108 
4096 time: 2.584872 
5120 time: 2.203696 
6144 time: 2.335194 
7168 time: 2.322517 
8192 time: 5.554941 
9216 time: 2.230817 

ha molto più senso se si ignorano alcune colonne - si salta dopo la (dimensioni L1) 32k , ma invece di saltare dopo 256k (dimensione L2), otteniamo un risultato troppo buono per 384 e saltiamo solo a 512k.L'ultimo salto è a 8 milioni (la mia dimensione LLC), ma 9k è rotto di nuovo.

Questo ci permette di individuare l'errore successivo - L'ANDing con maschera dimensione ha senso solo quando è una potenza di 2, altrimenti non ti avvolgi, ma ripeti alcuni degli ultimi indirizzi di nuovo (che finisce in ottimista risultati poiché è fresco nella cache).

provare a sostituire la ... & size_mask con % steps[i]/sizeof(int), il modulu è più costoso, ma se si vuole avere queste dimensioni si ha bisogno (o, in alternativa, un indice di esecuzione che viene azzerato ogni volta che supera la dimensione corrente)

+0

Grazie! Io uso invece j * 64, ma il risultato rimane confuso. Si prega di consultare il mio aggiornamento. –

+0

@ KanLiu - aggiornato la mia risposta – Leeor

+1

ottimo, funziona! Molte grazie. –

4

Penso che stareste meglio guardando l'istruzione CPUID. Non è banale, ma dovrebbero esserci informazioni sul web.

Inoltre, se si utilizza Windows, è possibile utilizzare la funzione GetLogicalProcessorInformation. Intendiamoci, è presente solo in Windows XP SP3 e versioni successive. Non so nulla di Linux/Unix.

+0

Questo è un buon modo. Ma voglio veramente capire come la cache funzioni davvero nei dettagli e perché il mio codice si comporti in questo modo. Grazie comunque. –

+1

@ KanLiu - Hai visto [questo articolo] (http://lwn.net/Articles/250967/)? Ha tutti i dettagli, inclusa la cache. –

+0

Grazie! Roba davvero buona –

2

Se usando GNU/Linux puoi solo leggere il contenuto dei file /proc/cpuinfo e per ulteriori dettagli /sys/devices/system/cpu/*. È abbastanza comune in UNIX non definire un'API, in cui un file semplice può fare comunque quel lavoro.

Vorrei anche dare un'occhiata alla fonte di util-linux, contiene un programma chiamato lscpu. Questo dovrebbe essere un esempio su come recuperare le informazioni richieste.

// aggiornare
http://git.kernel.org/cgit/utils/util-linux/util-linux.git/tree/sys-utils/lscpu.c

Se appena preso uno sguardo alla fonte loro. Fondamentalmente sta leggendo dal file sopra menzionato, questo è tutto. Quindi è assolutamente valido leggere anche da quei file, sono forniti dal kernel.

+0

Grazie. Come ho detto, le dimensioni e i livelli di cache non corrispondono al comportamento del mio codice, e voglio sapere il motivo. –

Problemi correlati