2016-06-14 19 views
1

Ich versuche, eine genaue Messung des Speicherzugriff auf verschiedene Cache-Ebene zu machen, und kam mit diesem Code für das Sondieren bis:Genaue Speicherzugriffszeitprüfung mit RDTSC und RDTSCP?

__asm__ __volatile__(
     "xor %%eax, %%eax \n" 
     "xor %%edi, %%edi \n" 
     "xor %%edx, %%edx \n" 
     /* time measurement */ 
     "lfence    \n" 
     "rdtsc    \n" 
     "shl $32, %%rdx  \n" 
     "or %%rdx, %%rax \n" 
     "movq %%rax, %%rdi \n" 
     /* memory access */ 
     "movq (%%rsi), %%rbx\n" 
     /* time measurement */ 
     "rdtscp    \n" 
     "shl $32, %%rdx  \n" 
     "or %%rdx, %%rax \n" 
     "movq %%rax, %%rsi \n" 
     "cpuid    \n" 
     : /* output operands */ 
     "=S"(t2), "=D"(t1) 
     : /* input operands */ 
     "S" (mem) 
     : /* clobber description */ 
     "ebx", "ecx", "edx", "cc", "memory" 
    ); 

jedoch der L1- und L2-Cache-Zugriff nur von 8 Zyklen unterscheiden, und den Ergebnissen sind zu viel schwank, so entschied ich mich, wie vielen Einfluss die umgebende Code (abgesehen von dem tatsächlichen Speicherzugriff) zu überprüfen, auf dem Timing hat:

__asm__ __volatile__(
     "xor %%eax, %%eax \n" 
     "xor %%edi, %%edi \n" 
     "xor %%edx, %%edx \n" 
     /* time measurement */ 
     "lfence    \n" 
     "rdtsc    \n" 
     "shl $32, %%rdx  \n" 
     "or %%rdx, %%rax \n" 
     "movq %%rax, %%rdi \n" 
     /* memory access */ 
     //"movq (%%rsi), %%rbx\n" 
     /* time measurement */ 
     "rdtscp    \n" 
     "shl $32, %%rdx  \n" 
     "or %%rdx, %%rax \n" 
     "movq %%rax, %%rsi \n" 
     "cpuid    \n" 
     : /* output operands */ 
     "=S"(t2), "=D"(t1) 
     : /* input operands */ 
     "S" (mem) 
     : /* clobber description */ 
     "ebx", "ecx", "edx", "cc", "memory" 
    ); 

die Ergebnisse sahen wie folgt aus:

./cache_testing 
From Memory: 42 
From L3: 46 
From L2: 40 
From L1: 38 

./cache_testing 
From Memory: 40 
From L3: 38 
From L2: 36 
From L1: 40 

Ich bin mir bewusst, dass ich im Moment nicht die verschiedenen Cache-Ebenen nach Zweck abrufe, aber ich frage mich, warum das Timing im Falle des fehlenden Speicherzugriffs so stark schwankt. Der Code wird als SCHED_FIFO mit der höchsten Priorität ausgeführt, an eine CPU angeheftet und sollte während des Betriebs nicht gesendet werden. Kann mir jemand sagen, ob ich meinen Code und damit die Ergebnisse in irgendeiner Weise verbessern kann?

+1

Die richtigen Zahlen für die Cache-Auslastung-> Nutzungslatenz bei Intel Haswell sind 4c für L1, 12c für L2, laut [Agner Fogs microarch pdf] (http://agner.org/optimize/). Eine gute Möglichkeit, dies zu messen (insbesondere für L1), ist das Posen-Jagen. Stellen Sie für L1 einfach einen Zeiger auf sich selbst und führen Sie 'mov (% rax),% rax 'in einer Schleife aus. Für L2 benötigen Sie eine große verkettete Liste, die nicht in L1 passt. –

Antwort

1

Um Ihren Messcode zu korrigieren, müssen Sie ein leeres Setup als Basislinie messen, um den Messaufwand zu reduzieren.

Denken Sie auch daran, dass der TSC Referenzzyklen zählt, nicht die Kerntaktzyklen. Damit dies funktioniert, müssen Sie sicherstellen, dass Ihre CPU immer mit der gleichen Geschwindigkeit läuft. (z. B. Turbo deaktivieren und eine Aufwärmschleife verwenden, um die CPU auf Höchstgeschwindigkeit zu bringen, dann sollten TSC-Zählungen mit Kernzyklen übereinstimmen, wenn Sie nicht übertakten.)

Das erklärt wahrscheinlich die Fluktuation.


Ich messe normalerweise Sachen mit Perf-Indikatoren, nicht RDTSC.

Aber ich denke, Sie sollten eine Serialisierungsanweisung (wie CPUID) vor dem ersten RDTSC verwenden. Die Verwendung einer CPUID nach dem zweiten RDTSC ist wahrscheinlich nicht sinnvoll. rdstcp for the second measurement is useful, da dies bedeutet, dass der Zeitstempel von dem Zeitpunkt kommt, an dem die Last ausgeführt wurde. (Das Handbuch sagt „ausgeführt“; IDK wenn das bedeutet, „im Ruhestand“ oder einfach nur buchstäblich durch den ein Last-Port ausgeführt.)

So IIRC, die beste Wahl ist:

# maybe set eax to something before CPUID 
cpuid 
rdtsc 
shl $32, %%rdx 
lea (%%rax, %%rdx), %%rsi 

... code under test 

# CPUID here, too, if you can only use rdtsc instead of rdtscp 
rdtscp 
shl $32, %%rdx 
or %%rdx, %%rax 
sub %%rsi, %%rax 
# time difference in RAX  

Wenn der Code im Test konkurriert für die gleichen ALU-Ports wie Shift/LEA, könnten Sie nur mov die Low 32 des ersten RDTSC Ergebnis zu einem anderen Register. Anstatt sich mit den hohen 32 überhaupt zu beschäftigen. Wenn Sie annehmen, dass der Unterschied in Zeitstempeln viel kleiner als 2^32 ist, benötigen Sie die hohen 32 Bits beider Zählungen nicht.

Ich habe gelesen, dass die Messung von winzigen Sequenzen wie diese auf modernen CPUs besser mit Leistungsindikatoren als mit dem TSC durchgeführt werden kann. Agner Fog's test programs enthalten Code für die Verwendung von Leistungsindikatoren innerhalb eines Programms, um etwas zu messen. Dadurch können Sie Kernzyklen unabhängig von Turbo oder Nicht-Turbo messen, da der Leistungszähler des Kerntaktzyklus tatsächlich zu einem pro physikalischem Taktzyklus zählt.

+0

Update: 'lfence; rdtsc' serialisiert es und ist effizienter als CPUID. –