Hallo,
ich schreibe im Moment verschiedene kleine Progrämmchen um mich im
Umgang mit modernem C++ zu üben. Da gibt es wirklich sehr sehr viel zu
lernen und verstehen.
Bei ersten Übungen zum Thema Concurrency bin ich auf einen sehr
interessanten Effekt gestoßen.
Ich hätte erwartet, dass std::thread sehr viel performanter ist, als
std::async, weil letzteres ja im Endeffekt nur eine aufwendigere
Schnittstelle zu ersterem ist. Der Unterschied ist groß genug, dass ich
gern nachfragen möchte, ob jemand weiß, warum?
1. Spalte Timing in ns, 2. Spalte Ergebnis der Berechnung
std::thread
generic 131282 9995155
atomic 132604 10000000
mutex 423830 10000000
std::async
generic 101916 9999564
atomic 103509 10000000
mutex 123793 10000000
3 Dinge finde ich besonders interessant:
1. async + mutex ist schneller als thread/nicht safe, das ist doch
verrückt?
2. Bei thread ist atomic drei mal schneller als mutex, bei async ist
atomic aber bloß 20 % schneller.
3. Vor allem aber wird die mutex Variante im Vergleich zur atomic
Variante überproportional schneller, was doch sogar unerwartet ist, wenn
einfach nur der Verwaltungsaufwand sinkt?
Das kompilierbare Programm hängt unten dran, ist ein klein wenig länger.
Herzliche Grüße
Timm
Edit: Beim mutex ist nur inc() gemutext, dec() wird ja nicht benutzt.
Edit2: Ich habe mir schon die Finger wund gesucht, aber ich habe den
Eindruck, dass wenn es um die eher neuen Features geht aktuelle Infos
wirklich sehr rar sind.
Der Unterschied zwischen den Varianten ist, dass der ctor von
std::thread immer(!) einen neuen Thread des OS erzeugt, während
std::async ein internes Threadpooling macht. Es wird also ggf. darauf
verzichtet, einen neuen OS Thread zu erzeugen, wenn noch einer im Pool
existiert.
Timm R. schrieb:> Ich hätte erwartet, dass std::thread sehr viel performanter ist, als> std::async, weil letzteres ja im Endeffekt nur eine aufwendigere> Schnittstelle zu ersterem ist. Der Unterschied ist groß genug, dass ich> gern nachfragen möchte, ob jemand weiß, warum?
Einen thread zu starten könnte teuer sein. Bei std::async sollten die
threads eines pools wieder verwendet werden. Das Verhalten würde ich so
etwarten. (und vor allem hätte dieser Pool, wahrscheinlich nicht mehr
threads, als CPUs.)
> 2. Bei thread ist atomic drei mal schneller als mutex, bei async ist> atomic aber bloß 20 % schneller.
Das könnte an zu hoher contention liegen. Wenn Du mehr threads am laufen
hast, als CPUs dann kann es sein, dass der scheduler evtl. auch mal
einen thread mit gelocketem mutex schlafen legt. Das ist natürlich Gift
für die Performance, soetwas würde man so nie designen und deswegen
kannst Du mit Deinen Messergebnissen auch herzlich wenig anfangen.
mfg Torsten
Ich habe das Program nur kurz überflogen und ich habe mich bis jetzt
noch nicht wirklich mit c++ threads und co beschäftigt. Meine erste
Vermutung ist, dass dein Program nicht das machst was du möchtest.
Du möchtest für jeden deiner Testfälle:
1. Zeit nehmen
2. "Threads starten"
3. auf Ergebnis warten
4. Zeit stoppen
du machst aber:
1. Zeit nehmen
2. "Threads starten"
3. Zeit stoppen
4. auf Ergebnis warten
Du misst also wie lange das erstellen und starten der Threads dauert.
Hallo Du Lexikon,
Wilhelm M. schrieb:> Der Unterschied zwischen den Varianten ist, dass der ctor von> std::thread immer(!) einen neuen Thread des OS erzeugt, während> std::async ein internes Threadpooling macht. Es wird also ggf. darauf> verzichtet, einen neuen OS Thread zu erzeugen, wenn noch einer im Pool> existiert.
bist du zufällig mit Stroustrup oder Sutter verwandt oder verschwägert
:-)
Das Wiederverwenden würde einiges erklären. Gerade in meinem Beispiel
würde das natürlich monstermäßig zuschlagen, weil ich so viele Threads
erzeuge und beerdige.
Zwei Fragen hätte ich: 1. Steht das irgendwo? (Nicht weil ich zweifle,
sondern weil in dem Text bestimmt noch andere relevante Dinge stehen)
2. Praktisch alle Tutorials verwenden std::thread, warum nur? std::async
scheint viel besser zu sein? Auch diese Eigentschaft spricht doch
absolut für std::async?
Besten Dank!
vlg
Timm
Timm R. schrieb:>> Zwei Fragen hätte ich: 1. Steht das irgendwo? (Nicht weil ich zweifle,> sondern weil in dem Text bestimmt noch andere relevante Dinge stehen)
Nicht so direkt. Das muss ja auch nicht so sein, denn es ist eine
Implementierungsfreiheit. Das schreibt keiner vor. Nur das Verhalten von
std::thread ist so definiert. Also: eine gute Implementierung wird das
so machen, eine schlechte muss es aber nicht.
In der Doku von std::async steht:
1
Behaves as if (2) is called with policy being std::launch::async | std::launch::deferred. In other words, f may be executed in another thread or it may be run synchronously when the resulting std::future is queried for a value.
Dieser Satz gibt m.E. alle Freiheiten ...
> 2. Praktisch alle Tutorials verwenden std::thread, warum nur? std::async> scheint viel besser zu sein? Auch diese Eigentschaft spricht doch> absolut für std::async?
(Vielleicht weil viel abgeschrieben wird ...)
Hallo,
vielen Dank schonmal an alle!
@Torsten
Der Effekt tritt ganz ähnlich auch bei 2 Threads auf:
34611 1998242
34005 2000000
41736 2000000
26220 1999971
26778 2000000
28366 2000000
Was ich auch noch interessant finde, ist dass es bei der ungeschützten
futures-Variante sytematisch weniger Kollisionen gibt (siehe zweite
Spalte: 1998242 vs 1999971, also 1800 vs. 30 Kollisionen)
vlg
Timm
Timm R. schrieb:> 2. Praktisch alle Tutorials verwenden std::thread, warum nur? std::async> scheint viel besser zu sein? Auch diese Eigentschaft spricht doch> absolut für std::async?
Weil die Tutorials die Verwendung von threads erklären wollen?
std::thread und std::async sind doch total unterschiedliche Dinge.
std::thread verwendest Du typischerweise dort, wo durchgängig einen
eigenen CPU kontext benötigst (z.B: als member eines pools). std::async
verwendest Du da, wo Du mal eben einen pool verwenden möchtest.
Mir hat "Programming with POSIX Threads" von David R. Butenhof sehr gut
gefallen. Die C++ thread API lehnt sich direkt an Posix an.
Timm R. schrieb:> @Torsten> Der Effekt tritt ganz ähnlich auch bei 2 Threads auf:>
Ja, es kann sein, dass Dein std::async überhaupt keinen thread startet
und die übergebene Funktion direkt ausführt. Das wäre in Deinem Fall ja
auch das effektivste :-)
Mit std::thread und std::async vergleicht man unterschiedliche Dinge:
ich meine jetzt nicht, dass std::async auch threads (oder tasks oder
sonst was) verwenden kann, sondern es handelt sich hierbei um
unterschiedliche Abstraktionsebenen.
std::thread ist low-level: ich will hier definitiv einen Thread
erzeugen.
std::async ist high(er)-level: ich will eine Aufgabe asynchron erledigen
lassen (wie, ist mir vollkommen egal) und das Ergebnis als Future später
irgendwann (oder auch nicht) abfragen.
Daran sieht man, dass low-level nicht immer geschickter ist, denn bei
vielen high-level Ansätzen konnten sich schon eine ganze Menge
intelligenter Leute bei der Realisierung der Abstraktion austoben ...
Hallo,
Torsten R. schrieb:> Timm R. schrieb:>> @Torsten>> Der Effekt tritt ganz ähnlich auch bei 2 Threads auf:>>> Ja, es kann sein, dass Dein std::async überhaupt keinen thread startet> und die übergebene Funktion direkt ausführt.
nee. Erstens würde ich das ja im Debugger sehen, zweitens würde dann ja
auch mein generic counter, ohne Schutzmaßnahmen, das richtige Ergebnis
liefern und drittens garantiert der Standard die asynchrone Ausführung.
§30.8.6 Nr.6
Throws: system_error if policy == launch::async and the implementation
is unable to start a new thread.
vlg
Timm
Timm R. schrieb:>> Ja, es kann sein, dass Dein std::async überhaupt keinen thread startet>> und die übergebene Funktion direkt ausführt.>> nee. Erstens würde ich das ja im Debugger sehen, zweitens würde dann ja> auch mein generic counter, ohne Schutzmaßnahmen, das richtige Ergebnis> liefern und drittens garantiert der Standard die asynchrone Ausführung.
Stimmt, dass solte dann nicht passieren.
> §30.8.6 Nr.6> Throws: system_error if policy == launch::async and the implementation> is unable to start a new thread.
Das widerspricht ja nicht einer Implementierung, die überhaupt keinen
thread startet (siehe Zitat von Wilhelm). Auf einer single processor
Maschine wäre das sogar eine sehr sinnvolle Implementierung.
Torsten R. schrieb:> Timm R. schrieb:>>> Ja, es kann sein, dass Dein std::async überhaupt keinen thread startet>>> und die übergebene Funktion direkt ausführt.>>>> nee. Erstens würde ich das ja im Debugger sehen, zweitens würde dann ja>> auch mein generic counter, ohne Schutzmaßnahmen, das richtige Ergebnis>> liefern und drittens garantiert der Standard die asynchrone Ausführung.>> Stimmt, dass solte dann nicht passieren.
genau, also bei meiner Implementierung definitiv Threads, habs auch
extra noch mal überprüft.
Und egal mit wievielen Threads, immer ist der „Fehler“ im ungeschützten
Counter mit std::thread signifikant größer.
34611 1998242
26220 1999971
Ist wohl nicht so wichtig, aber mich würde schon interessieren, ob der
Compiler da mit irgendwelchen Tricks etwas dreht ...
vlg
Timm
Hallo,
hier noch mal zwei prominente Ergebnisse meiner Recherchen, die mich
hatten glauben lassen std::async sei weniger cool, als es in
Wirklichkeit ist und soweit ich die Antworten und mein Experiment
verstehe auch wohl schlicht so nicht richtig sind:
https://stackoverflow.com/questions/25814365/when-to-use-stdasync-vs-stdthreads
Frage: When to use std::async vs std::threads?
Antwort: ... but std::async is rather limited in the current standard.
...
Currently, std::async is probably best suited to handling either very
long running computations or long running IO for fairly simple programs.
It doesn't guarantee low overhead though (and in fact the way it is
specified makes it difficult to implement with a thread pool behind the
scenes), so it's not well suited for finer grained workloads. For that
you either need to roll your own thread pools using std::thread ...
https://bartoszmilewski.com/2011/10/10/async-tasks-in-c11-not-quite-there-yet/
If you expected std::async to be just syntactic sugar over thread
creation, you can stop reading right now, because that’s what it is.
Dazu im Kontrast die Feststellung Wilhelms, die sich ja absolut mit
meiner Beobachtung deckt:
Daran sieht man, dass low-level nicht immer geschickter ist, denn bei
vielen high-level Ansätzen konnten sich schon eine ganze Menge
intelligenter Leute bei der Realisierung der Abstraktion austoben ...
Erleuchtete Grüße
Timm