Frage

Das folgende Stück Code, um uns von unserem Lehrer gegeben wurde, so dass wir einige Algorithmen Leistung messen können:

#include <stdio.h>
#include <unistd.h>

static unsigned cyc_hi = 0, cyc_lo = 0;

static void access_counter(unsigned *hi, unsigned *lo) {
    asm("rdtsc; movl %%edx,%0; movl %%eax,%1"
    : "=r" (*hi), "=r" (*lo)
    : /* No input */
    : "%edx", "%eax");
}

void start_counter() {
    access_counter(&cyc_hi, &cyc_lo);
}

double get_counter() {
    unsigned ncyc_hi, ncyc_lo, hi, lo, borrow;
    double result;

    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;

    return result;
}

Allerdings muß ich diesen Code mit unterschiedlichen CPU Frequenzen Maschinen tragbar sein. Dafür versuche ich die CPU-Frequenz der Maschine zu berechnen, wo der Code wie folgt ausgeführt wird:

int main(void)
{
    double c1, c2;

    start_counter();

    c1 = get_counter();
    sleep(1);
    c2 = get_counter();

    printf("CPU Frequency: %.1f MHz\n", (c2-c1)/1E6);
    printf("CPU Frequency: %.1f GHz\n", (c2-c1)/1E9);

    return 0;
}

Das Problem ist, dass das Ergebnis immer 0 ist, und ich kann nicht verstehen, warum. Ich bin mit Linux (Arch) als Gast auf VMware.

Auf einer Maschine Freund (MacBook), ist es zu einem gewissen Grad arbeiten; Ich meine, ist das Ergebnis größer als 0, aber es ist variabel, da die CPU-Frequenz nicht festgelegt ist (wir haben versucht, es zu beheben, aber aus irgendeinem Grund, den wir nicht in der Lage, es zu tun). Er hat eine andere Maschine, die Linux (Ubuntu) als Host ausgeführt wird und es auch Berichte, 0. Diese Regeln aus dem Problem auf der virtuelle Maschine zu sein, was ich dachte, es war die Frage an erster Stelle.

Alle Ideen, warum dies geschieht und wie kann ich das Problem beheben?

War es hilfreich?

Lösung

Okay, da die andere Antwort nicht hilfreich war, werde ich versuchen, auf detaillierter zu erläutern. Das Problem ist, dass eine moderne CPU-Befehle außerhalb der Reihenfolge ausgeführt werden können. Der Code beginnt als so etwas wie:

rdtsc
push 1
call sleep
rdtsc

Moderne CPUs tun nicht unbedingt ausführen Anweisungen in ihrer ursprünglichen Reihenfolge though. Trotz Ihrer ursprünglichen Bestellung, die CPU ist (meistens) frei, das ausführen wie:

rdtsc
rdtsc
push 1
call sleep

In diesem Fall ist es klar, warum der Unterschied zwischen den beiden rdtscs wäre (zumindest sehr nahe) 0. Um das zu verhindern, müssen Sie einen Befehl auszuführen, dass die CPU nie rearrange zur Ausführung außerhalb der Reihenfolge. Die häufigste Anweisung zur Verwendung für das heißt CPUID. Die andere Antwort, die ich verbunden sein (wenn der Speicher dient) etwa von dort aus starten, um den notwendigen Schritten CPUID richtig verwenden / effektiv für diese Aufgabe.

Natürlich ist es möglich, dass Tim Beitrag richtig war, und du bist auch sehen Probleme wegen einer virtuellen Maschine. Dennoch, wie es jetzt steht, gibt es keine Garantie, dass der Code richtig, auch auf echte Hardware funktionieren.

Edit:, warum der Code würde Arbeit: gut, vor allem die Tatsache, dass Anweisungen können werden von Reihenfolge ausgeführt aus garantiert nicht, dass sie < em> wird sein. Zweitens ist es möglich, dass (zumindest einige Implementierungen von) sleep Anweisungen enthalten Serialisierung, die rdtsc verhindern, um es neu angeordnet werden, während andere dies nicht tun (oder sie enthalten, sondern sie nur unter bestimmten auszuführen (aber nicht näher bezeichnet) Umstände).

Was Sie bleibt, ist Verhalten, das mit fast jedem erneuten Kompilierung ändern könnte, oder auch nur zwischen einem Lauf und dem nächsten. Es könnte sehr genaue Ergebnisse dutzende Male in Folge erzeugen, dann für einige scheitern (fast) vollständig unerklärlichen Grund (zum Beispiel etwas, das vollständig in einem anderen Prozess geschehen).

Andere Tipps

Ich kann nicht sicher sagen, was genau ist falsch mit Ihrem Code, aber Sie tun ziemlich viel unnötige Arbeit für eine solche einfache Anweisung. Ich empfehle Ihnen, Ihre rdtsc Code wesentlich zu vereinfachen. Sie brauchen keine 64-Bit-Mathematik trägt dein Selbst zu tun, und Sie nicht das Ergebnis dieser Operation als Doppel speichern müssen. Sie haben keine separate Ausgänge in Ihrer Inline-asm verwenden müssen, Sie GCC sagen kann, EAX und EDX zu verwenden.

Dies ist eine stark vereinfachte Version dieses Codes:

#include <stdint.h>

uint64_t rdtsc() {
    uint64_t ret;

# if __WORDSIZE == 64
    asm ("rdtsc; shl $32, %%rdx; or %%rdx, %%rax;"
        : "=A"(ret)
        : /* no input */
        : "%edx"
    );
#else
    asm ("rdtsc" 
        : "=A"(ret)
    );
#endif
    return ret;
}

Auch sollten Sie berücksichtigen die Werte ausdrucken Sie dieses bekommen, so dass Sie sehen können, wenn Sie 0s raus, oder etwas anderes.

Wie bei VMWare, werfen Sie einen Blick auf die Zeit zu halten spec (PDF Link ) sowie dieses Themas . TSC Anweisungen werden (je nach Gastbetriebssystem):

  • Bestand direkt auf die reale Hardware (PV guest)
  • Count Zyklen , während die VM auf dem Host-Prozessor ausgeführt wird (Windows / etc)

Beachten Sie, in # 2 die , während die VM auf dem Host-Prozessor ausgeführt wird. Das gleiche Phänomen würde für Xen, als auch, wenn ich mich richtig erinnere. Im Wesentlichen können Sie erwarten, dass der Code soll wie auf einem paravirtualisierte Gast erwartet. Wenn emuliert, sein völlig unvernünftig zu erwarten, Hardware wie Konsistenz.

Sie haben vergessen zu verwenden volatile in Ihrer asm-Anweisung , so dass Sie den Compiler doch sagen, dass die asm Anweisung die gleiche Leistung jedes Mal, wie eine reine Funktion erzeugt. (volatile ist nur implizit für asm Aussagen ohne Ausgänge).

Dies erklärt, warum Sie bekommen genau Null. Der Compiler optimiert end-start zu 0 bei der Kompilierung durch CSE (Common-subexpression Beseitigung)

Siehe meine Antwort auf Get CPU Zykluszahl? für die __rdtsc() intrinsischen und @ Mysticial Antwort dort hat Arbeits GNU C Inline-asm, die ich hier zitieren würde:

// prefer using the __rdtsc() intrinsic instead of inline asm at all.
uint64_t rdtsc(){
    unsigned int lo,hi;
    __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
    return ((uint64_t)hi << 32) | lo;
}

Dies funktioniert korrekt und effizient für 32- und 64-Bit-Code.

hmmm Ich bin nicht positiv, aber ich vermute, dass das Problem innerhalb dieser Linie sein kann:

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

Ich bin misstrauisch, wenn Sie sicher so groß Multiplikationen in einem „unsigned“ ausführen können ... nicht, dass oft eine 32-Bit-Zahl? ... nur die Tatsache, dass Sie nicht sicher von 2 ^ 32 multiplizieren konnte und als extra „* 4“ in den 2 ^ 30 am Ende bereits Hinweise auf diese Möglichkeit hinzu anhängen hatte ... müssen Sie möglicherweise konvertieren jede Subkomponente hallo und lo zu einem doppelten (anstelle eines einzelnen ganz am Ende) und die Multiplikation zu tun mit den beiden Doppel

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top