Erwägen:
#include <time.h>
#include <unistd.h>
#include <iostream>
using namespace std;
const int times = 1000;
const int N = 100000;
void run() {
for (int j = 0; j < N; j++) {
}
}
int main() {
clock_t main_start = clock();
for (int i = 0; i < times; i++) {
clock_t start = clock();
run();
cout << "cost: " << (clock() - start) / 1000.0 << " ms." << endl;
//usleep(1000);
}
cout << "total cost: " << (clock() - main_start) / 1000.0 << " ms." << endl;
}
Hier ist der Beispielcode. In den ersten 26 Iterationen der Zeitschleife run
kostet die Funktion ungefähr 0,4 ms, aber dann reduzieren sich die Kosten auf 0,2 ms.
Wenn das nicht usleep
kommentiert ist, dauert die Verzögerungsschleife für alle Läufe 0,4 ms und beschleunigt nie. Warum?
Der Code wird mit kompiliert g++ -O0
(keine Optimierung), sodass die Verzögerungsschleife nicht entfernt wird. Es läuft auf Intel (R) Core (TM) i3-3220- CPU mit 3,30 GHz und Ubuntu 14.04.1 LTS (Trusty Tahr) mit 3.13.0-32-Generika .
c++
linux
performance
benchmarking
phyxnj
quelle
quelle
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.gcc -O2
oder kompilieren (da Ihr Code C ++ ist)g++ -O2
.usleep
ist außerhalb des ZeitfenstersAntworten:
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 a
usleep
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ährendusleep
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 vonclock()
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 alsmain
(da gccmain
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, sodassmain
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.
quelle
N
um das 100-fache und benutze dencpufreq-info
Befehl. Ich habe festgestellt, dass die CPU immer noch mit der Mindestfrequenz arbeitet, wenn der Code ausgeführt wird.usleep
nicht kommentiert? Es läuft für mich mit N = 10000000 hoch. (Ich benutze,grep MHz /proc/cpuinfo
da ich nie dazu gekommen bin, cpufreq-utils auf diesem Computer zu installieren). Eigentlich habe ich gerade herausgefunden,cpupower frequency-info
was zeigt, was cpufreq-info für einen Kern getan hat.cpupower
scheint standardmäßig nur Kern 0 zu sein.grep MHz /proc/cpuinfo
zeigt tatsächlich einen Anstieg der CPU-Frequenz.cpufreq-info
Vielleicht überwachen Sie einen zufälligen Kern der CPU. Ich denke, Sie haben Recht, vielleicht ist dies die Ursache des Problems.Ein Aufruf von
usleep
kann zu einem Kontextwechsel führen oder auch nicht. Wenn dies der Fall ist, dauert es länger als wenn dies nicht der Fall ist.quelle
usleep
gibt die CPU freiwillig frei, daher sollte es sicher einen Kontextwechsel geben (auch wenn das System inaktiv ist), nicht wahr?usleep
gewechselt , bevor der aufgerufene Prozess fortgesetzt wird. Daher kann die Verschmutzung von Caches / TLBs / Verzweigungsprädiktoren minimal sein.schedule
heraus, wie lange es bis zum nächsten Schritt dauert , und entscheidet dann, wie er warten soll, möglicherweise plant er etwas anderes, möglicherweise mithilfe eines Hardware-Timers, möglicherweise nicht.usleep
, würden einige Leute sagen, dass es keinen Kontextwechsel gab, selbst wenn die Hardware (mit einerhlt
Anweisung) für kurze Zeit in den Ruhezustand ging . In diesem Fall gibt es definitiv eine minimale Cache- / TLB-Verschmutzung und IIRC keine TLB-Ungültigkeit. (Ich vergesse genau, wie die Seitentabellen für den Kernelmodus funktionieren, aber ich denke nicht, dass der gesamte TLB bei jedem Systemaufruf weggeblasen werden muss).