Warum läuft dieser einfache Lambda innerhalb von std :: thread konsistent schneller als innerhalb der Hauptfunktion mit gcc 4.9.2?

9

Das folgende Snippet verwendet einen Befehlszeilenparameter, der die Anzahl der zu erzeugenden Threads darstellt, um gleichzeitig eine einfache for-Schleife auszuführen.

Wenn die übergebenen Argumente 0 sind, wird kein std::thread erzeugt.

Bei gcc 4.9.2 dauert ./snippet 0 im Durchschnitt 10% länger als ./snippet 1 , dh die Version, die ein std::thread zum Ausführen der Schleife erzeugt, ist schneller als die Version, die die Schleife innerhalb main ausführt. .

Weiß jemand was los ist? clang-4 zeigt dieses Verhalten überhaupt nicht (Version mit einem std::thread ist langsamer), gcc 6.2 hat die Version mit einem std::thread run nur etwas schneller (wenn man die Mindestzeit für zehn Versuche als Messwert verwendet) .

Hier ist das Snippet: ScopedNanoTimer ist nur ein einfacher RAII-Timer. Ich kompiliere mit -g -O3 -pthread -std=c++11 .

%Vor%

BEARBEITEN

Den Vorschlägen in den Kommentaren folgend habe ich versucht, dem Compiler die Tatsache zu verschleiern, dass die Anzahl der Iterationen a priori für den logischen Zweig bekannt war, in dem n_threads == 0 steht. Ich habe die relevante Zeile in

geändert %Vor%

Ich habe auch die externe for-Schleife mit 10 Ausführungen und allen Erwähnungen des ScopedNanoTimers entfernt. Diese Änderungen werden nun im obigen Snippet widergespiegelt.

Ich habe mit den oben angegebenen Flags kompiliert und mehrmals auf einer Workstation mit Debian Linux, Kernel Version 3.16.39-1 + deb8u2 und Prozessor Intel (R) Core (TM) i7-4790 CPU @ 3.60GHz, Quad ausgeführt -Ader. Alle anderen Programme wurden geschlossen, cpu throttling / intel speed-step / turbo-boost deaktiviert und cpu governor policy auf "performance" gesetzt. Internetverbindung wurde deaktiviert.

Der Trend ist immer, dass das Kompilieren mit gcc-4.9.2 die Version ohne std::thread s etwa 10% schneller ist als die Version, die einen Thread erzeugt. clang-4 hat stattdessen das entgegengesetzte (erwartete) Verhalten.

Die folgenden Messungen haben mich davon überzeugt, dass das Problem in suboptimalen gcc-4.9.2-Optimierungen liegt und nicht auf Kontextwechsel oder schlechte Qualität der Messungen zurückzuführen ist. Mit diesem gesagt, nicht einmal godbolt Compiler Explorer zeigt mir klar, was gcc tut, ich denke nicht, die Frage beantwortet.

Zeit- und Kontextwechsel messen mit g ++ - 4.9.2

%Vor%

Zeit- und Kontextwechsel messen mit clang ++ - 4.0

%Vor%     
blue 30.05.2017, 14:00
quelle

1 Antwort

2

Ich denke, Sie könnten das Opfer einer schlechten Testprobe sein. Ich habe versucht, dieses Verhalten zu reproduzieren, und nachdem ich jede Option etwa 10 Mal für jede Option ausgeführt hatte, stellte ich fest, dass ich Zeiten mit relativ hoher Varianz empfing. Ich habe noch einige Tests mit / usr / bin / time -v durchgeführt und festgestellt, dass die Ausführungszeit des Programms ziemlich gut mit der Anzahl der unfreiwilligen Kontextwechsel korreliert, die das Programm durchlaufen hat.

%Vor%

Ich denke, Sie haben Ihre Benchmarks in Zeiten variabler Arbeitsbelastung für Ihr Betriebssystem einfach ausgeführt. Wie Sie mit meinen obigen Zeitdaten sehen können, wurden die Zeiten von mehr als 20 Sekunden in Zeiten hoher Last für das Betriebssystem gesammelt. Ebenso wurden Zeiten unter 19 Sekunden in Zeiten geringer Last gesammelt.

Logischerweise sehe ich, warum die Schleife, die einen Thread absetzt, langsamer laufen sollte. Der Aufwand für die Erstellung eines Threads ist hoch im Vergleich zur Operation der Schleife, bei der einfach eine Zahl inkrementiert wird. Dies sollte eine Erhöhung der Benutzerzeit zur Ausführung des Programms erforderlich machen. Das Problem ist, dass diese Erhöhung der Benutzerzeit im Vergleich zur Ausführungszeit der gesamten Schleife wahrscheinlich vernachlässigbar ist. Sie erstellen nur 10 zusätzliche Threads im Laufe Ihres Programmlebens, und das Ausführen von Berechnungen innerhalb dieser Threads sollte nur sehr geringe Unterschiede aufweisen, wenn Sie diese Berechnungen hauptsächlich durchführen. Sie führen im Laufe Ihres Programms Milliarden anderer Operationen durch, die den Anstieg der Benutzerzeit verdecken. Wenn Sie wirklich die Erstellungszeit eines Threads vergleichen möchten, würden Sie ein Programm schreiben, das viele Threads erstellt und nicht viel mehr tut. Sie sollten auch darauf achten, Benchmarks wie diese in Umgebungen mit möglichst wenigen Hintergrundprozessen auszuführen.

Das mag nicht die Gesamtheit des Problems sein, aber ich glaube, es ist immer noch wichtig, darüber nachzudenken.

    
Jayson Boubin 30.05.2017 18:54
quelle

Tags und Links