2013-10-02 8 views
9

Ich möchte ein Programm schreiben, um meine Cache-Größe (L1, L2, L3) zu bekommen. Ich kenne die allgemeine Idee davon.Schreiben Sie ein Programm, um CPU-Cache-Größen und Ebenen zu erhalten

  1. Weisen ein großes Array jedes Mal davon unterschiedlicher Größe
  2. Zugang Teil.

Also schrieb ich ein kleines Programm. Hier ist mein Code:

#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; 
} 

jedoch das Ergebnis so seltsam ist:

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 

Meine CPU ist Intel (R) Core (TM) i3-2350M. L1-Cache: 32 KB (für Daten), L2-Cache 256 KB, L3-Cache 3072 KB. Scheint, als ob es keiner Regel folgt. Ich kann keine Informationen über Cache-Größe oder Cache-Level erhalten. Kann jemand Hilfe geben? Danke im Voraus.

Update: Follow @Leeor Beratung, verwende ich j*64 statt j*16. Neue Ergebnisse:

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 

Zwei Peaks, 4096K und 8192K. Immer noch komisch.

Antwort

5

Ich bin nicht sicher, ob dies das einzige Problem hier ist, aber es ist definitiv das größte - Ihr Code würde sehr schnell die HW-Stream-Prefetcher auslösen, so dass Sie fast immer in L1 oder L2 Latenzen getroffen werden.

Weitere Details finden sich hier - http://software.intel.com/en-us/articles/optimizing-application-performance-on-intel-coret-microarchitecture-using-hardware-implemented-prefetchers

Für Ihre Benchmark sollten Sie sie entweder deaktivieren (über BIOS oder andere Mittel), oder zumindest Ihre Schritte machen mehr von j*16 (* 4 Byte pro int = ersetzt 64B, eine Cache-Zeile - eine klassische Einheitsschritt für den Stromdetektor) mit j*64 (4 Cache-Zeilen). Der Grund dafür ist, dass der Prefetcher zwei Prefetches pro Stream-Anfrage ausführen kann, also vor dem Code läuft, wenn Sie Unit-Strides ausführen, kann aber immer noch ein bisschen vor Ihnen sein, wenn Ihr Code über 2 Zeilen springt, aber mit längerem mehr nutzlos wird Sprünge (3 ist nicht gut wegen deines modulu, du benötigst einen Teiler von step_size)

Aktualisiere die Fragen mit den neuen Resultaten und wir können herausfinden, ob es irgendetwas anderes hier gibt.


EDIT1: Ok, lief ich den festen Code und bekam -

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 

Es macht viel mehr Sinn, wenn man ein paar Spalten ignorieren - Sie springen nach der 32k (L1 Größe) , aber anstatt nach 256k (L2-Größe) zu springen, erhalten wir für 384 ein zu gutes Ergebnis und springen erst bei 512k.Der letzte Sprung ist bei 8M (meine LLC-Größe), aber 9k ist wieder gebrochen.

Dies ermöglicht es uns, den nächsten Fehler zu erkennen - ANDing mit Größenmaske macht nur Sinn, wenn es eine Potenz von 2 ist, sonst wickeln Sie nicht herum, sondern stattdessen einige der letzten Adressen erneut (was in optimistisch endet) Ergebnisse, da es im Cache frisch ist).

versuchen, die ... & size_mask mit % steps[i]/sizeof(int) ersetzen, ist die modulu teurer, aber wenn Sie diese Größen haben möchten Sie müssen es (oder alternativ einen laufenden Index, der auf Null gesetzt wird, wenn es um die aktuelle Größe überschreitet)

+0

Danke !. Ich benutze stattdessen j * 64, aber das Ergebnis bleibt verwirrend. Bitte sehen Sie mein Update. –

+0

@KanLiu - aktualisiert meine Antwort – Leeor

+1

großartig, es funktioniert! Danke vielmals. –

4

Ich denke, Sie wären besser dran auf die CPUID Anweisung. Es ist nicht trivial, aber es sollte Informationen im Internet geben. Wenn Sie unter Windows arbeiten, können Sie auch die Funktion GetLogicalProcessorInformation verwenden. Beachten Sie, dass es nur in Windows XP SP3 und höher vorhanden ist. Ich weiß nichts über Linux/Unix.

+0

Das ist ein guter Weg. Aber ich möchte wirklich verstehen, wie Cache wirklich im Detail funktioniert und warum mein Code sich so verhält. Danke trotzdem. –

+1

@KanLiu - Haben Sie [diesen Artikel] (http://lwn.net/Articles/250967/) gesehen? Es hat alle Details, einschließlich Caches 'n Zeug. –

+0

Vielen Dank! Wirklich gutes Zeug. –

2

Wenn Sie Mit GNU/Linux können Sie einfach den Inhalt der Dateien /proc/cpuinfo lesen und für weitere Details /sys/devices/system/cpu/*. Unter UNIX ist es üblich, keine API zu definieren, wo eine einfache Datei diesen Job trotzdem ausführen kann.

Ich würde auch einen Blick auf die Quelle der nehmen util-linux, enthält es ein Programm namens lscpu. Dies sollte Ihnen ein Beispiel geben, wie Sie die benötigten Informationen abrufen können.

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

Wenn Sie einen Blick auf die Quelle ihrer genommen. Es liest im Grunde genommen aus der oben genannten Datei, das ist alles. Daher ist es absolut richtig, auch von diesen Dateien zu lesen, sie werden vom Kernel bereitgestellt.

+0

Danke. Wie gesagt, die Größe und die Ebenen des Caches stimmen nicht mit dem Verhalten meines Codes überein, und ich möchte den Grund wissen. –