Nach 26 Iterationen, Rampen Linux die CPU auf die maximale Taktfrequenz bis da Ihr Prozess seine volle verwendet Zeitscheibe ein paar Mal in Folge.
Wenn Sie anstelle der Wanduhrzeit Leistungsindikatoren verwenden, werden Sie feststellen, dass die Kerntaktzyklen pro Verzögerungsschleife konstant bleiben, was bestätigt, dass dies nur ein Effekt von DVFS ist (das alle modernen CPUs verwenden, um mit mehr Energie zu arbeiten). meistens effiziente Frequenz und Spannung).
Wenn Sie auf einem Skylake mit Kernel-Unterstützung für den neuen Energieverwaltungsmodus (bei dem die Hardware die volle Kontrolle über die Taktrate übernimmt) getestet haben , würde der Hochlauf viel schneller erfolgen.
Wenn Sie es für eine Weile auf einer Intel-CPU mit Turbo laufen lassen , wird sich die Zeit pro Iteration wahrscheinlich wieder leicht erhöhen, sobald die Taktrate bei thermischen Grenzwerten wieder auf die maximal anhaltende Frequenz reduziert werden muss. (Weitere Informationen zu Turbo, mit dem die CPU schneller ausgeführt werden kann als bei Hochleistungs-Workloads, finden Sie unter Warum kann meine CPU die Spitzenleistung in HPC nicht aufrechterhalten ?)
Die Einführung von ausleep
verhindert, dass der CPU-Frequenzregler von Linux die Taktrate erhöht, da der Prozess selbst bei minimaler Frequenz keine 100% ige Last erzeugt. (Das heißt, die Heuristik des Kernels entscheidet, dass die CPU schnell genug für die Arbeitslast läuft, die darauf ausgeführt wird.)
Kommentare zu anderen Theorien :
Betreff: Davids Theorie, dass ein möglicher Kontextwechsel usleep
Caches verschmutzen könnte : Das ist im Allgemeinen keine schlechte Idee, aber es hilft nicht, diesen Code zu erklären.
Die Cache / TLB-Verschmutzung ist für dieses Experiment überhaupt nicht wichtig . Im Timing-Fenster befindet sich im Grunde nichts anderes als das Ende des Stapels, das den Speicher berührt. Die meiste Zeit wird in einer winzigen Schleife (1 Zeile Befehls-Cache) verbracht, die nur einen int
Stapelspeicher berührt . Jede mögliche Cache-Verschmutzung während usleep
ist nur ein winziger Bruchteil der Zeit für diesen Code (realer Code wird anders sein)!
Im Detail für x86:
Der Aufruf an sich clock()
selbst kann einen Cache-Miss verursachen, aber ein Code-Fetch-Cache-Miss verzögert die Startzeitmessung, anstatt Teil dessen zu sein, was gemessen wird. Der zweite Aufruf von clock()
wird fast nie verzögert, da er im Cache noch heiß sein sollte.
Die run
Funktion befindet sich möglicherweise in einer anderen Cache-Zeile als main
(da gcc main
als "kalt" markiert ist , wird sie weniger optimiert und zusammen mit anderen kalten Funktionen / Daten platziert). Wir können ein oder zwei Anweisungs-Cache-Fehler erwarten . Sie befinden sich jedoch wahrscheinlich immer noch auf derselben 4k-Seite, sodass main
der potenzielle TLB-Fehler ausgelöst wurde, bevor der zeitgesteuerte Bereich des Programms eingegeben wurde.
gcc -O0 kompiliert den OP-Code wie folgt (Godbolt Compiler Explorer) : Der Schleifenzähler bleibt im Speicher des Stapels.
Die leere Schleife hält den Schleifenzähler im Stapelspeicher, sodass auf einer typischen Intel x86-CPU die Schleife dank der Speicherweiterleitungslatenz, die Teil add
eines Speicherziels ist (Lesen), mit einer Iteration pro ~ 6 Zyklen auf der IvyBridge-CPU des OP ausgeführt wird -modify-write). 100k iterations * 6 cycles/iteration
beträgt 600.000 Zyklen, was den Beitrag von höchstens ein paar Cache-Fehlern dominiert (jeweils ~ 200 Zyklen für Code-Abruf-Fehler, die verhindern, dass weitere Anweisungen ausgegeben werden, bis sie behoben sind).
Die Ausführung außerhalb der Reihenfolge und die Weiterleitung von Speichern sollten den potenziellen Cache-Fehler beim Zugriff auf den Stapel (als Teil der call
Anweisung) größtenteils verbergen .
Selbst wenn der Schleifenzähler in einem Register gehalten wurde, sind 100.000 Zyklen eine Menge.
usleep()
da es möglicherweise unterbrochen wird oder nichts unternimmt, da Ihr Parameter nicht gültig ist. Dies würde jegliches Timing unzuverlässig machen.