Domanda

Il seguente pezzo di codice è stato dato a noi dal nostro istruttore in modo che potessimo misurare alcune prestazioni algoritmi:

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

Tuttavia, ho bisogno di questo codice per essere portabile su macchine con diverse frequenze di CPU. Per questo, sto cercando di calcolare la frequenza della CPU della macchina in cui il codice viene eseguito in questo modo:

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

Il problema è che il risultato è sempre 0 e non riesco a capire perché. Sto lavorando con Linux (Arco) come ospite su VMware.

sulla macchina di un amico (MacBook) si sta lavorando in una certa misura; Voglio dire, il risultato è più grande di 0, ma è variabile, perché la frequenza della CPU non è fisso (abbiamo provato a risolvere il problema, ma per qualche motivo non siamo in grado di farlo). Ha una macchina diversa, che è in esecuzione Linux (Ubuntu) come ospite e riporta anche 0. Questo esclude il problema di essere sulla macchina virtuale, che ho pensato che fosse il problema in un primo momento.

Tutte le idee perché questo sta accadendo e come posso risolvere il problema?

È stato utile?

Soluzione

D'accordo, dal momento che l'altra risposta non è stata utile, cercherò di spiegare in modo più dettagliato. Il problema è che una CPU moderna può eseguire istruzioni fuori ordine. Il tuo codice inizia come qualcosa di simile a:

rdtsc
push 1
call sleep
rdtsc

CPU moderne fanno non necessariamente eseguire istruzioni nel loro ordine originale però. Nonostante l'ordine originale, la CPU è (quasi) gratuito per l'esecuzione che, proprio come:

rdtsc
rdtsc
push 1
call sleep

In questo caso, è chiaro il motivo per cui la differenza tra i due rdtscs sarebbe (almeno molto vicino a) 0. Per evitare questo, è necessario eseguire un'istruzione che la CPU non Riorganizza eseguire fuori ordine. L'istruzione più comune per l'uso di ciò è CPUID. L'altra risposta ho linkato dovrebbe (se la memoria non serve) iniziare all'incirca da lì, circa le misure necessarie per utilizzare correttamente CPUID / efficace per questo compito.

Naturalmente, è possibile che Tim Post era a destra, e si sta anche vedere problemi a causa di una macchina virtuale. Tuttavia, così com'è adesso, non c'è alcuna garanzia che il codice funzionerà correttamente anche su hardware reale.

Edit: il motivo per cui il codice farebbe di lavoro: bene, prima di tutto, il fatto che le istruzioni possono essere eseguite fuori ordine non garantisce che < em> essere. In secondo luogo, è possibile che (almeno alcune implementazioni di) sleep contengono la serializzazione di istruzioni che impediscono rdtsc di essere riorganizzate attorno ad esso, mentre altri non lo fanno (o possono contenerli, ma solo li eseguono sotto specifica (ma non specificato) le circostanze).

Che cosa si è lasciato con un comportamento che potrebbe cambiare con quasi tutti i ri-compilazione, o anche solo tra una corsa e l'altra. Si potrebbe produrre risultati estremamente precisi decine di volte di fila, poi falliscono per alcuni (quasi) completamente ragione inspiegabile (per esempio, qualcosa che è accaduto in qualche altro processo interamente).

Altri suggerimenti

Non posso dire con certezza che cosa è esattamente sbagliato con il tuo codice, ma si sta facendo un po 'di lavoro inutile per una semplice istruzione tale. Vi consiglio di semplificare il codice rdtsc sostanzialmente. Non è necessario fare 64-bit matematica trasporta la vostra auto, e non è necessario per memorizzare il risultato di tale operazione come una doppia. Non è necessario utilizzare uscite separate in asm inline, si può dire GCC di usare EAX e EDX.

Ecco una versione molto semplificata di questo codice:

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

Inoltre si dovrebbe prendere in considerazione la stampa i valori che stai ricevendo da questo modo si può vedere se si sta uscendo 0s, o qualcos'altro.

Per quanto riguarda VMWare, date un'occhiata alla il tempo mantenendo spec (PDF Link ), nonché questa discussione . istruzioni TSC sono (a seconda del sistema operativo guest):

  • passati direttamente al reale hardware (guest PV)
  • Il conte cicli , mentre la VM è in esecuzione su processore host (Windows / etc)

Si noti, in 2 # , mentre la VM è in esecuzione su processore host. Lo stesso fenomeno sarebbe andato per Xen, così, se non ricordo male. In sostanza, ci si può aspettare che il codice dovrebbe funzionare come previsto su un guest paravirtualizzato. Se emulato, la sua del tutto irragionevole aspettarsi hardware come consistenza.

dimenticato di utilizzo volatile nell'istruzione asm , in modo che stai dicendo al compilatore che la dichiarazione asm produce lo stesso output ogni volta, come una pura funzione. (volatile è implicito solo per le istruzioni asm senza uscite.)

Questo spiega il motivo per cui stai ricevendo esattamente a zero:. Il compilatore ottimizzato end-start a 0 al momento della compilazione, attraverso la CSE (eliminazione common-subexpression)

Vedere la mia risposta su Get conteggio dei cicli della CPU? per l'intrinseca __rdtsc() e @ risposta di Mysticial ci deve lavorare GNU C asm inline, che cito qui:

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

Questo funziona correttamente e in modo efficiente per il codice a 32 e 64 bit.

hmmm io non sono positive, ma ho il sospetto che il problema può essere all'interno di questa linea:

risultato = (doppia) hi * (1 << 30) * 4 + lo;

Sono sospetto se si può tranquillamente effettuare tali moltiplicazioni enormi in un "unsigned" ... non è che spesso un numero a 32 bit? ... solo il fatto che non si poteva tranquillamente moltiplicare per 2 ^ 32 e ha dovuto aggiungerla come un extra "* 4" aggiunto alla 2 ^ 30 alla fine già accenni a questa possibilità ... potrebbe essere necessario convertire ogni hi sottocomponente ed ecco ad una doppia (anziché uno solo alla fine) e fare la moltiplicazione utilizzando le due doppie

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top