2016-04-16 4 views
0

Ich habe Verwirrung in dieser speziellen Linie ->Messprogrammlaufzeit mit Zykluszähler

result = (double) hi * (1 << 30) * 4 + lo; 

des folgenden Codes:

void access_counter(unsigned *hi, unsigned *lo) 
// Set *hi and *lo to the high and low order bits of the cycle 
// counter. 
{ 
    asm("rdtscp; movl %%edx,%0; movl %%eax,%1" // Read cycle counter 
     : "=r" (*hi), "=r" (*lo)    // and move results to 
     : /* No input */      // the two outputs 
     : "%edx", "%eax"); 
} 

double get_counter() 
// Return the number of cycles since the last call to start_counter. 
{ 
    unsigned ncyc_hi, ncyc_lo; 
    unsigned hi, lo, borrow; 
    double result; 

    /* Get cycle counter */ 
    access_counter(&ncyc_hi, &ncyc_lo); 
    lo = ncyc_lo - cyc_lo; 
    borrow = lo > ncyc_lo; 
    hi = ncyc_hi - cyc_hi - borrow; 
    result = (double) hi * (1 << 30) * 4 + lo; 
    if (result < 0) { 
    fprintf(stderr, "Error: counter returns neg value: %.0f\n", result); 
    } 
    return result; 
} 

Das, was ich nicht verstehen kann, ist das, warum hallo Wesen multipliziert mit 2^30 und dann 4? und dann niedrig hinzugefügt? Bitte erläutern Sie, was in dieser Codezeile passiert. Ich weiß, was Hallo und Low enthalten.

+0

Haben Sie sich die Dokumente für rdtscp angesehen? Es gibt eine 64-Bit-Nummer zurück. Die unteren 32 Bits in eax und die höheren 32 Bits in edx. In einer sinnvollen Implementierung würde access_counter eine 64-Bit-Ganzzahl zurückgeben. Warum das zu einem Gleitpunkt wird, kann ich mir nicht vorstellen. –

+0

Und während ich dabei bin, ist asm falsch geschrieben. 1) Es ändert Ecx, ohne den Compiler über Ausgabe oder Clobber zu informieren (sehr schlecht). 2) Es hat 2 unnötige mov-Anweisungen (verschwendet sowohl Zeit als auch wertvolle Register). Wie wäre es mit 'unsigned int a; unsigned long long b = __builtin_ia32_rdtscp (&a); '?) ​​Wenn Sie eine 64-Bit-Zahl für (die scheinbar undefinierte?) cyc_lo & cyc_hi verwendet haben, erleichtert das auch das Subtrahieren von newtime - oldtime. –

Antwort

1

Die kurze Antwort:

Diese Linie stellt sich eine 64-Bit-Ganzzahl, die als 2 32-Bit-Werte in eine Gleitkommazahl gespeichert wird.

Warum verwendet der Code nicht nur eine 64-Bit-Ganzzahl? Nun, gcc hat 64-Bit-Nummern für eine lange Zeit unterstützt, aber vermutlich ist dieser Code älter. In diesem Fall besteht die einzige Möglichkeit, so große Zahlen zu unterstützen, darin, sie in eine Gleitkommazahl zu bringen.

Die lange Antwort:

Zuerst müssen Sie, wie rdtscp Werke verstehen. Wenn diese Assembleranweisung aufgerufen wird, führt sie 2 Dinge aus:

1) Setzt ecx auf IA32_TSC_AUX MSR. Meiner Erfahrung nach bedeutet dies im Allgemeinen, dass ecx auf Null gesetzt wird. 2) Setzt edx: eax auf den aktuellen Wert des Zeitstempelzählers des Prozessors. Dies bedeutet, dass die unteren 64 Bits des Zählers in eax und die oberen 32 Bits in edx gehen.

In diesem Sinne, schauen wir uns den Code an. Beim Aufruf von get_counter wird access_counter edx in 'ncyc_hi' und eax in 'ncyc_lo' setzen. Dann wird get_counter tun:

lo = ncyc_lo - cyc_lo; 
borrow = lo > ncyc_lo; 
hi = ncyc_hi - cyc_hi - borrow; 

Was macht das?

Da die Zeit in 2 verschiedenen 32-Bit-Zahlen gespeichert ist, müssen wir, um herauszufinden, wie viel Zeit verstrichen ist, etwas arbeiten, um den Unterschied zwischen der alten und der neuen Zeit zu finden. Wenn dies geschehen ist, wird das Ergebnis (wiederum unter Verwendung von 2 32-Bit-Nummern) in hi/lo gespeichert.

Was bringt uns schließlich zu Ihrer Frage.

result = (double) hi * (1 << 30) * 4 + lo; 

Wenn wir 64-Bit-Integer, Umwandlung 2 32bit Werte zu einem einzigen 64-Bit-Wert wie folgt aussehen würde verwenden:

unsigned long long result = hi; // put hi into the 64bit number. 
result <<= 32;     // shift the 32 bits to the upper part of the number 
results |= low;     // add in the lower 32bits. 

Wenn Sie vielleicht nicht zu Bitverschiebung verwendet, es sieht aus wie das wird helfen. Wenn lo = 1 und hoch = 2, dann als Hex-Zahlen:

result = hi; 0x0000000000000002 
result <<= 32; 0x0000000200000000 
result |= low; 0x0000000200000001 

Aber wenn wir die Compiler übernehmen keine 64-Bit-Integer nicht unterstützt, das wird nicht funktionieren. Während Gleitkommazahlen so große Werte enthalten können, unterstützen sie keine Verschiebung. Also müssen wir einen Weg finden, um 'hi' links um 32Bits, ohne mit der linken Verschiebung zu verschieben.

Ok, das Verschieben um 1 nach links ist also genau dasselbe wie das Multiplizieren mit 2.Die Verschiebung nach links um 2 ist die gleiche wie die Multiplikation mit 4. Die Verschiebung nach links um [weggelassen ...] Die Verschiebung um 32 nach links ist die gleiche wie die Multiplikation mit 4.294.967.296.

Durch einen erstaunlichen Zufall, 4294967296 == (1 < < 30) * 4.

warum schreiben Sie es also in dieser komplizierten Art und Weise? Nun, 4.294.967.296 ist eine ziemlich große Zahl. In der Tat ist es zu groß, um in eine 32-Bit-Ganzzahl zu passen. Das heißt, wenn wir es in unseren Quellcode schreiben, könnte ein Compiler, der 64-Bit-Ganzzahlen nicht unterstützt, Schwierigkeiten haben, herauszufinden, wie er verarbeitet werden soll. So geschrieben, kann der Compiler beliebige Fließkomma-Anweisungen erzeugen, die er benötigt, um an dieser wirklich großen Zahl zu arbeiten.

Warum der aktuelle Code ist falsch:

Es sieht aus wie Variationen dieses Codes wurden für eine lange Zeit um das Internet wandern. Ursprünglich (ich nehme an) access_counter wurde mit rdtsc anstelle von rdtscp geschrieben. Ich werde nicht versuchen, den Unterschied zwischen den beiden zu beschreiben (google sie), außer darauf hinzuweisen, dass rdtsc nicht ecx setzt und rdtscp tut. Wer auch immer rdtsc in rdtscp geändert hat, wusste das offenbar nicht und konnte den Inline-Assembler nicht anpassen. Obwohl Ihr Code trotzdem gut funktioniert, könnte er stattdessen etwas seltsames machen. Um es zu beheben, könnten Sie tun:

asm("rdtscp; movl %%edx,%0; movl %%eax,%1" // Read cycle counter 
    : "=r" (*hi), "=r" (*lo)     // and move results to 
    : /* No input */       // the two outputs 
    : "%edx", "%eax", "%ecx"); 

Während dies funktioniert, ist es nicht optimal. Register sind eine wertvolle und knappe Ressource auf i386. Dieses winzige Fragment verwendet 5 davon. Mit einer geringfügigen Änderung:

asm("rdtscp" // Read cycle counter 
    : "=d" (*hi), "=a" (*lo) 
    : /* No input */ 
    : "%ecx"); 

Jetzt haben wir zwei weniger Montage Aussagen, und wir verwenden nur drei Register.

Aber auch das ist nicht das Beste, was wir tun können. In der (vermutlich lange) Zeit, da dieser Code geschrieben wurde, hat gcc sowohl die Unterstützung für 64-Bit-Integer und eine Funktion hinzugefügt, um die tsc zu lesen, so dass Sie brauchen nicht asm zu verwenden überhaupt:

unsigned int a; 
unsigned long long result; 

result = __builtin_ia32_rdtscp(&a); 

‚eine 'ist der (nutzlose?) Wert, der in ecx zurückgegeben wurde. Der Funktionsaufruf erfordert es, aber wir können den zurückgegebenen Wert einfach ignorieren.

Anstatt also so etwas wie dies zu tun (was ich Ihren vorhandenen Code annehmen, der Fall ist):

unsigned cyc_hi, cyc_lo; 

access_counter(&cyc_hi, &cyc_lo); 
// do something 
double elapsed_time = get_counter(); // Find the difference between cyc_hi, cyc_lo and the current time 

können wir tun:

unsigned int a; 
unsigned long long before, after; 

before = __builtin_ia32_rdtscp(&a); 
// do something 
after = __builtin_ia32_rdtscp(&a); 
unsigned long long elapsed_time = after - before; 

Dies ist kürzer, verwendet keine Hard- Assembler zu verstehen, ist leichter zu lesen, zu pflegen und produziert den bestmöglichen Code.

Aber es erfordert eine relativ neue Version von gcc.