Domanda
#include <stdio.h>
static inline unsigned long long tick()
{
unsigned long long d;
__asm__ __volatile__ ("rdtsc" : "=A" (d) );
return d;
}
int main()
{
long long res;
res=tick();
res=tick()-res;
printf("%d",res);
return 0;
}
Ho compilato questo codice con gcc con -O0 -O1 -O2 -O3 ottimizzazioni. E ho sempre trovato 2000-2500 cicli. Qualcuno può spiegare il motivo di questa uscita? Come spendere questi cicli?
Primo funzione "tick" è sbagliato. Questo è giusto .
Un'altra versione della funzione "tick"
static __inline__ unsigned long long tick()
{
unsigned hi, lo;
__asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}
Questa è l'Assemblea codice per -O3
.file "rdtsc.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "%d"
.text
.p2align 4,,15
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
subl $40, %esp
movl %ecx, -16(%ebp)
movl %ebx, -12(%ebp)
movl %esi, -8(%ebp)
movl %edi, -4(%ebp)
#APP
# 6 "rdtsc.c" 1
rdtsc
# 0 "" 2
#NO_APP
movl %edx, %edi
movl %eax, %esi
#APP
# 6 "rdtsc.c" 1
rdtsc
# 0 "" 2
#NO_APP
movl %eax, %ecx
movl %edx, %ebx
subl %esi, %ecx
sbbl %edi, %ebx
movl %ecx, 4(%esp)
movl %ebx, 8(%esp)
movl $.LC0, (%esp)
call printf
movl -16(%ebp), %ecx
xorl %eax, %eax
movl -12(%ebp), %ebx
movl -8(%ebp), %esi
movl -4(%ebp), %edi
movl %ebp, %esp
popl %ebp
leal -4(%ecx), %esp
ret
.size main, .-main
.ident "GCC: (Debian 4.3.2-1.1) 4.3.2"
.section .note.GNU-stack,"",@progbits
Si tratta di CPU
processor : 0
vendor_id : GenuineIntel
cpu family : 15
model : 4
model name : Intel(R) Xeon(TM) CPU 3.00GHz
stepping : 3
cpu MHz : 3000.105
cache size : 2048 KB
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 5
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss constant_tsc up pebs bts pni
bogomips : 6036.62
clflush size : 64
Soluzione
Ho provato il codice su diverse distribuzioni Linux in esecuzione su diverse CPU Intel (certamente tutti più recente rispetto al Pentium 4 HT 630 in cui sembra essere utilizzando). In tutti questi test ho ottenuto valori compresi tra 25 e 50 cicli.
La mia unica ipotesi che è coerente con tutta l'evidenza è che si sta eseguendo il sistema operativo all'interno di una macchina virtuale, piuttosto che su metallo nudo, e TSC sta ottenendo virtualizzato.
Altri suggerimenti
Ci sono una serie di ragioni per ottenere un gran numero:
- Il sistema operativo ha fatto un cambio di contesto, e il processo sono messo a dormire.
- seek Si è verificato un disco e il processo sono messo a dormire.
- ... qualsiasi di una gran numero di motivi per cui il processo potrebbe avere ignorato.
Si noti che rdtsc
non è particolarmente affidabile per tempi senza lavoro, perché:
- velocità del processore possono cambiare, e quindi, la lunghezza di un ciclo (quando misurata in secondi) cambia.
- processori diversi possono avere valori diversi per la TSC per un dato istante di tempo.
La maggior parte dei sistemi operatings hanno un orologio ad alta precisione o il metodo di sincronizzazione. clock_gettime
su Linux, ad esempio, in particolare gli orologi monotone. (Comprendere troppo la differenza tra una parete-orologio e un orologio monotona:. Un orologio da parete in grado di tornare indietro - anche in UTC) In Windows, credo che la raccomandazione è QueryHighPerformanceCounter
. In genere questi orologi forniscono più che sufficiente precisione per la maggior parte delle esigenze.
Inoltre, guardando il gruppo, sembra che stai ricevendo solo 32-bit della risposta:. Non vedo %edx
sempre salvato dopo rdtsc
Il funzionamento del vostro codice, ottengo tempi da 120-150 ns per clock_gettime
utilizzando CLOCK_MONOTONIC
, e 70-90 cicli per rdtsc (~ 20 ns a tutta velocità, ma ho il sospetto che il processore ha un clock verso il basso, e questo è realmente circa 50 ns ). (Su un laptop desktop (SSH maledettamente, ha dimenticato che la macchina ero su!), Che si trova a circa un costante utilizzo della CPU del 20%) che la macchina non è impantanato?
Sembra che il vostro sistema operativo disattivato esecuzione di RDTSC nello spazio utente. E l'applicazione dispone di passare al kernel e ritorno, che prende un sacco di cicli.
Questo è dal sviluppatori Intel Software manuale:
In modalità protetta o virtuale 8086, la disabilitazione time stamp (TSD) bandiera registro CR4 limita l'uso delle istruzioni RDTSC come segue. Quando la bandiera TSD è chiaro, l'istruzione RDTSC può essere eseguita in qualsiasi livello di privilegio; quando la bandiera è impostato, l'istruzione può essere eseguita solo a livello di privilegi 0. (quando in real-indirizzo modalità, l'istruzione RDTSC è sempre abilitato.)
Modifica
Risposta a commento di Aix, spiego, perché TSD è probabilmente il motivo per qui.
Lo so solo queste possibilità per un programma di eseguire una singola istruzione più lunga del solito:
- Esecuzione sotto qualche emulatore,
- utilizzando il codice di auto-modificato,
- cambio di contesto,
- interruttore kernel.
Per prima 2 motivi non può di solito ritardare l'esecuzione per più di qualche centinaio di cicli. 2000-2500 cicli sono più tipico per cambio di contesto / kernel. Ma è praticamente impossibile per la cattura di un cambio di contesto più volte nello stesso posto. Così dovrebbe essere l'interruttore del kernel. Il che significa che entrambi i programmi è in esecuzione in debugger o RDTSC non è consentito in modalità utente.
La ragione più probabile per OS a RDTSC disabilitare potrebbe essere la sicurezza. Ci sono stati tentativi di utilizzare RDTSC per rompere programmi di crittografia.
cache istruzioni perdere? (Questa è la mia ipotesi)
Inoltre, possibilmente,
Passa alla hypervisor in un sistema virtualizzato? Resti di programma di bootstrap (tra cui attività di rete sulla stessa CPU)?
Per Thanatos:. Sui sistemi più recenti, rispetto al 2008, rdtsc () è un orologio da parete e non varia con passi di frequenza
Si può provare questo piccolo codice?
int main()
{
long long res;
fflush(stdout); // chnage the exact timing of stdout, in case there is something to write in a ssh connection, together with its interrupts
for (int pass = 0; pass < 2; pass++)
{
res=tick();
res=tick()-res;
}
printf("%d",res); // ignore result on first pass, display the result on second pass.
return 0;
}
Solo un'idea - forse queste due istruzioni RDTSC vengono eseguiti su diversi core? valori RDTSC possono leggermente variare tra nuclei.