Dopo 26 iterazioni, Linux accelera la CPU fino alla massima velocità di clock poiché il tuo processo utilizza la sua porzione di tempo pieno un paio di volte di seguito.
Se controllassi con i contatori delle prestazioni anziché con il tempo dell'orologio da parete, vedresti che i cicli di clock del core per ciclo di ritardo sono rimasti costanti, confermando che è solo un effetto di DVFS (che tutte le CPU moderne usano per funzionare a una maggiore energia- frequenza e voltaggio efficienti per la maggior parte del tempo).
Se hai testato su uno Skylake con il supporto del kernel per la nuova modalità di gestione dell'alimentazione (dove l'hardware assume il pieno controllo della velocità di clock), l'accelerazione avverrebbe molto più velocemente.
Se lo lasci in esecuzione per un po' su una CPU Intel con Turbo, probabilmente vedrai il tempo per iterazione aumentare di nuovo leggermente una volta che i limiti termici richiedono che la velocità di clock si riduca fino alla frequenza massima sostenuta. (Vedi Perché la mia CPU non riesce a mantenere le massime prestazioni in HPC per ulteriori informazioni su Turbo che consente alla CPU di funzionare più velocemente di quanto possa sostenere per carichi di lavoro ad alta potenza.)
Ti presentiamo un usleep
impedisce al regolatore di frequenza della CPU di Linux di aumentare la velocità di clock, poiché il processo non genera un carico del 100% anche alla frequenza minima. (Cioè l'euristica del kernel decide che la CPU sta funzionando abbastanza velocemente per il carico di lavoro che è in esecuzione su di essa.)
commenti su altre teorie :
re:la teoria di David secondo cui un potenziale cambio di contesto da usleep
potrebbe inquinare le cache:non è una cattiva idea in generale, ma non aiuta a spiegare questo codice.
L'inquinamento della cache/TLB non è affatto importante per questo esperimento . Praticamente non c'è nulla all'interno della finestra temporale che tocchi la memoria oltre alla fine dello stack. La maggior parte del tempo viene trascorsa in un piccolo ciclo (1 riga di cache di istruzioni) che tocca solo un int
della memoria dello stack. Qualsiasi potenziale inquinamento della cache durante usleep
è una piccola frazione del tempo per questo codice (il codice reale sarà diverso)!
Più in dettaglio per x86:
La chiamata a clock()
stesso potrebbe mancare nella cache, ma un errore nella cache del recupero del codice ritarda la misurazione dell'ora di inizio, piuttosto che far parte di ciò che viene misurato. La seconda chiamata a clock()
non subirà quasi mai ritardi, perché dovrebbe essere ancora attivo nella cache.
Il run
la funzione potrebbe trovarsi in una riga della cache diversa da main
(dal momento che gcc segna main
come "freddo", quindi viene ottimizzato di meno e posizionato con altre funzioni/dati freddi). Possiamo aspettarci uno o due fallimenti nella cache delle istruzioni. Probabilmente sono ancora nella stessa pagina 4k, quindi main
avrà attivato la potenziale perdita del TLB prima di entrare nella regione a tempo del programma.
gcc -O0 compilerà il codice dell'OP in qualcosa di simile a questo (Godbolt Compiler explorer):mantenendo il contatore del ciclo in memoria nello stack.
Il ciclo vuoto mantiene il contatore del ciclo nella memoria dello stack, quindi su una tipica CPU Intel x86 il ciclo viene eseguito a un'iterazione per ~6 cicli sulla CPU IvyBridge dell'OP, grazie alla latenza di inoltro dello store che fa parte di add
con una destinazione di memoria (lettura-modifica-scrittura). 100k iterations * 6 cycles/iteration
è di 600k cicli, che domina il contributo di al massimo un paio di cache miss (~200 cicli ciascuno per code-fetch miss che impediscono l'emissione di ulteriori istruzioni finché non vengono risolte).
L'esecuzione fuori ordine e lo store-forwarding dovrebbero principalmente nascondere la potenziale perdita della cache durante l'accesso allo stack (come parte del call
istruzioni).
Anche se il loop-counter fosse tenuto in un registro, 100k cicli sono tanti.
Una chiamata al usleep
può comportare o meno un cambio di contesto. Se lo fa, ci vorrà più tempo che se non lo fa.