Wozu async deferred?
-
Also ich seh es immer noch so, dass der nicht zwischen single- und multithreading unterscheidet. Die einzige Sache ist, dass async synchron oder asynchron ausgeführt wird, also entweder im aktuellen Thread oder in einem nebenläufigen Thread.
Multithreading in "Hochform" nutzt ja alle Kerne aus, dementsprechend also auch mindestens so viele Threads. Async führt ja keine wirkliche Parallelisierung hinzu (das Wort impliziert für mich parallele Abarbeitung EINER Aufgabe, also mindestens zwei Threads für das, was man async als Funktion übergibt).
Das löst also soweit ich denke nur einen Teil der Probleme. Nett wäre das trotzdem, aber nur die Spitze vom Eisberg, so wie ich das sehe.
-
Skym0sh0 schrieb:
... und auch Quellen dazu angeben?
http://channel9.msdn.com/Shows/The+Knowledge+Chamber/C-A-Language-for-Modern-Times Ungeduldige können zu 22:28 springen
Eisflamme schrieb:
Async führt ja keine wirkliche Parallelisierung hinzu (das Wort impliziert für mich parallele Abarbeitung EINER Aufgabe, also mindestens zwei Threads für das, was man async als Funktion übergibt).
Ah, jetzt habe ichs verstanden. So genial ist async dann auch nicht, dass es magisch deinen Code analysiert und automatisch parallelisiert. Du musst schon von Hand async für alles Parallelisierbare schreiben. Aber es ist genial genug um den Code automatisch zu synchronisieren. Und das ist fast genau so gut.
-
Also ums nochmal zusammenzufassen:
Bei parallelisierbaren Funktionen nutzen wir ab jetzt std::async und zur laufzeit entscheidet die Laufzeitumgebung wann welche CPU (bzw. welcher Core) diese Funktion laufen lässt - Ohne dass man als Programmeirer nochwas definieren muss oder irgendwelchen Mutexe, Semaphoren oder sonstwas einbauen muss. Und gleichzeitig skaliert diese Variante relativ beliebig hoch (insoweit überhaupt soviel Rechenpower gebraucht wird, in dem Beispielvideo war es ja nicht nötig, die Flipfunktion zu parallelisieren).Hab ich das so richtig verstanden?!?
-
Natürlich nicht.
-
Zur Compilezeit entscheidet der Compiler ob std::async parallel läuft oder nicht.
Da es parallel laufen kann, müssen wir als Programmierer dafür sorgen, dass keine Race Conditions entstehen (Mutex etc.).
-
nwp3 schrieb:
[...]
Wirklich interessante Erklärung, danke auch für den Link zum Video.
Nathan schrieb:
Zur Compilezeit entscheidet der Compiler ob std::async parallel läuft oder nicht.
Die Entscheidung kann durchaus zur Laufzeit fallen. Darum lässt der Standard der Implementierung bei den Flags
launch::async|launch::deferred
so viel Freiheit. Es könnte also durchaus sein, dass intelligente Implementierungen die aktuelle CPU-Auslastung berücksichtigen. Momentan sind sie leider noch relativ dumm (wie gesagt macht gcc einfach immerlaunch::deferred
).
-
Ist
launch::async|launch::deferred
der "Fix" für das Problem, dasslaunch::async
z.B. keine Work-Stealing Scheduler zulässt, da der Standard bekloppterweise garantiert dass einlaunch::async
Task in seinem eigenen Thread ausgeführt wird?@nwp3:
Ich verstehe deine Euphorie nicht ganz.Ein Programmierer sollte bei jedem noch so kleinen parallelisierbaren Codestück async + future::wait benutzen und der Compiler optimiert das zu einem normalen Funktionsaufruf, ohne Overhead. Dann, wenn das Programm wächst oder jemand Threadpools effizienter implementiert hat, kann es sofort davon profitieren. Ich erwarte durch async Performancesprünge und erhöhte Skalierbarkeit, die mit jeder Prozessorgeneration noch zunehmen wird. Soweit ich weiß ist das auch einzigartig bei Programmiersprachen, dass der Compiler über das Threading entscheidet. Die Idee ist unglaublich genial.
Er.
"Wenn es nix nützt dann schades es auch nicht" ist nicht richtig. Ne Future zu initialisieren ist alles andere als gratis. Nicht wenn der Compiler es schon "wegoptimiert", nur dann kann auch nichts mehr parallelisiert werden. Und wenn der Compiler es drinnen lässt, dann braucht man schon "Payloads" von etlichen 100 CPU-Zyklen um den Overhead wiedergutzumachen.
DasDer Compiler sieht sich den Code an und entscheidet, ob sich der Overhead lohnt, und zwar viel besser, als der Programmierer es je könnte, insbesondere in Verbindung mit Profiling.
ist leider auch ein unrealistischer Wunschtraum. Das kann kein Compiler jemals können. Weil der Compiler nämlich die Parameter nicht kennen kann mit der die Funktion aufgerufen wird (*).
Und abgesehen davon behaupte ich, dass es sehr sehr viel Aufwand darstellen würde, einem Compiler beizubringen hier überhaupt irgendwas zu analysieren, und abhängig vom Ergebnis dann einen direkten Funktionsaufruf zu generieren oder eben einen asynchronen.Sich einfach zu denken "ah toll, da kümmert sich der Compiler drum, jippie!" ist zwar verlockend, aber in dem Fall mMn. total unrealistisch.
*: Überleg dir mal wie ein "normaler" (single-threaded) Sortieralgorithmus aufgebaut ist. Da hast du typischerweise ein "if (count > limit) teile_und_herrsche(); else insertion_sort();" drinnen. Dieses "if" kann der Compiler unmöglich für dich einfügen. Nicht bei single-threaded, und genau so wenig wenn man es auf mehrere Threads aufteilt.
-
ps:
nwp3 schrieb:
Soweit ich weiß ist das auch einzigartig bei Programmiersprachen, dass der Compiler über das Threading entscheidet. Die Idee ist unglaublich genial.
Es wäre einzigartig und genial, wenn es existieren würde bzw. überhaupt sinnvoll möglich wäre. Tut es aber nicht bzw. ist es nicht.
Und... die ganze std::async Sache ist, verglichen mit manchen anderen Implementierungen, eher wenig mächtig und auch nicht so toll angenehm zu verwenden.
Cilk (aus 1994!) hat z.B. die wesentlich angenehmere Syntax, und gibt dem Compiler bzw. der Runtime auch viel mehr Freiheiten - wodurch diverse Optimierungen viel einfacher (bzw. überhaupt erst möglich) werden.
async
in C# ist wesentlich mächtiger und hat ebenso eine angenehmere Syntax.Die Idee von Thread-Pools (und mehr ist
std::async
nicht), ist auch schon sehr sehr alt. Und es gibt auch schon genügend Implementierungen in diversen Libraries in diversen Sprachen. Das einzig neue oder besonders tolle anstd::async
ist dasstd::
.
-
Ich glaube dieses std::async kann man nicht ernsthaft verwenden. Zu unzuverlässig im Verhalten das Ding. IMHO nur was für Spielzeug-Anwendungen.
-
Also ich kann die allgemeine Ablehnung von async nicht nachvollziehen. Klar gibts da noch einige Mängel. Ein Mangel, der noch nicht genannt wurde, ist, dass es kein future.then gibt.
hustbaer schrieb:
Ne Future zu initialisieren ist alles andere als gratis. Nicht wenn der Compiler es schon "wegoptimiert", nur dann kann auch nichts mehr parallelisiert werden. Und wenn der Compiler es drinnen lässt, dann braucht man schon "Payloads" von etlichen 100 CPU-Zyklen um den Overhead wiedergutzumachen.
Völlig korrekt. Und ich behaupte Compiler werden 2014/15 diese Entscheidung halbwegs ordentlich umsetzen.
hustbaer schrieb:
Ist
launch::async|launch::deferred
der "Fix" für das Problem, dasslaunch::async
z.B. keine Work-Stealing Scheduler zulässt, da der Standard bekloppterweise garantiert dass einlaunch::async
Task in seinem eigenen Thread ausgeführt wird?Würde ich nicht sagen. "Eigener Thread" wiederspricht nicht Threadpool. Aber launch::async ist ziemlich bekloppt, ja. Nur kann man ohne launch::async Leute wie
Cilk-Fan schrieb:
Ich glaube dieses std::async kann man nicht ernsthaft verwenden. Zu unzuverlässig im Verhalten das Ding. IMHO nur was für Spielzeug-Anwendungen.
niemals zufrieden stellen. Mit launch::async verhält sich std::async genau so dumm und "zuverlässig" wie jede andere create_thread-Funktion. Mit der Zeit wird man erkennen wie doof launch::async ist und es einfach weglassen.
hustbaer schrieb:
Cilk (aus 1994!) hat z.B. die wesentlich angenehmere Syntax, und gibt dem Compiler bzw. der Runtime auch viel mehr Freiheiten - wodurch diverse Optimierungen viel einfacher (bzw. überhaupt erst möglich) werden.
async
in C# ist wesentlich mächtiger und hat ebenso eine angenehmere Syntax.Beispiele bitte, mit Hinweis was da optimiert werden kann, was async nicht kann.
hustbaer schrieb:
[async so umsetzten wie nwp3 geschrieben hat ist anstrengend/unmöglich]
Der Compiler muss für beliebige Funktionen und Eingaben entscheiden, ob diese Funktion mehr oder weniger als 100 Zyklen (obda. der Overhead für einen Thread) brauchen wird. Die Eingabe kennt er, weil er es zur Laufzeit macht. Ohne Rekursion kann man Assemblerbefehle zählen. Syscalls kosten 100, bei Verzweigungen nimmt man den längsten Pfad, bei Schleifen... muss man die Anzahl der Schleifendurchläufe irgendwie aus dem Code und der Eingabe ableiten. Bei Zufallszahlen hat man wohl keine Chance und machts einfach asynchron. Bei Rekursion ist das schwieriger. Längsten Pfad der Funktion ohne Rekursion bestimmen, Anzahl der Rekursionen aus der Eingabe abschätzen... Ja, es ist schwierig. Aber nicht unmöglich, da ist kein Halteproblem oder so drin, das kriegt man alles durch Simulation raus. Außerdem muss man nicht immer richtig liegen.
Wenn es jemand hinkriegt ist es jedenfalls toll, wenn nicht, dann hat man immernoch dieselbe Qualität einer "zuverlässigen" Threadfunktion auf eine plattformunabhängige Art.
-
nwp3 schrieb:
hustbaer schrieb:
Ne Future zu initialisieren ist alles andere als gratis. Nicht wenn der Compiler es schon "wegoptimiert", nur dann kann auch nichts mehr parallelisiert werden. Und wenn der Compiler es drinnen lässt, dann braucht man schon "Payloads" von etlichen 100 CPU-Zyklen um den Overhead wiedergutzumachen.
Völlig korrekt. Und ich behaupte Compiler werden 2014/15 diese Entscheidung halbwegs ordentlich umsetzen.
Ich wette dagegen.
nwp3 schrieb:
hustbaer schrieb:
Ist
launch::async|launch::deferred
der "Fix" für das Problem, dasslaunch::async
z.B. keine Work-Stealing Scheduler zulässt, da der Standard bekloppterweise garantiert dass einlaunch::async
Task in seinem eigenen Thread ausgeführt wird?Würde ich nicht sagen. "Eigener Thread" wiederspricht nicht Threadpool.
Ich hab' ja auch nicht Thread-Pool geschrieben sondern Work-Stealing Scheduler. Und bei Work-Stealing kann es sein dass ein und der selbe Pool-Thread erst an Teil 1 von Task A arbeitet, dann an Task B und dann wieder mit Teil 2 von Task A weiter macht.
Wenn der Standard nun aber "eigener Thread" sagt, dann heisst das für mich, dass man sich darauf verlassen kann, dass Task A und Task B nicht im selben Thread ausgeführt werden. Ein Programm das sich auf diese Garantie verlässt, z.B. indem es thread-local Variablen verwendet, könnte mit einem Work-Stealing Scheduler also Probleme bekommen.
Bzw. genaugenommen heisst "eigener Thread" für mich sogar dass es ein "frischer" Thread ist. Also einer dessen thread-local Variablen bei Start des Tasks alle Default-Werte haben.
nwp3 schrieb:
hustbaer schrieb:
Cilk (aus 1994!) hat z.B. die wesentlich angenehmere Syntax, und gibt dem Compiler bzw. der Runtime auch viel mehr Freiheiten - wodurch diverse Optimierungen viel einfacher (bzw. überhaupt erst möglich) werden.
async
in C# ist wesentlich mächtiger und hat ebenso eine angenehmere Syntax.Beispiele bitte, mit Hinweis was da optimiert werden kann, was async nicht kann.
Cilk kennt keine Futures, und ermöglicht es einem nicht das (bei Cilk implizite) "Wait Handle" (=die Future) irgendwie zu transferieren. Soweit ich weiss muss eine Funktion auch zwingend auf alle "gespawnten" Funktionen warten bevor sie zurückkehren kann. (Bzw. wenn nicht ist trivial festzustellen ob sie es tut - wenn in allen Pfaden ein "sync" vorkommt tut sie es, und sonst eben nicht.)
Dadurch wird es möglich bestimmte Dinge auf dem Stack der aufrufenden Funktion zu allokieren. z.B. die gespawnten Funktoren und die List-Nodes mit der diese in die Liste der wartenden Tasks eingeklinkt werden.Ich weiss allerdings nicht ob diese Dinge wirklich so umgesetzt sind. So lange keine anderen Gründe dagegen sprechen, würde ich es auf jeden Fall so umsetzen.
nwp3 schrieb:
hustbaer schrieb:
[async so umsetzten wie nwp3 geschrieben hat ist anstrengend/unmöglich]
Der Compiler [...blaaaaaah]
Mir ist schon klar dass man hier diverse Heuristiken verwenden kann.
Du hast aber behauptet dass der Compiler es besser abschätzen kann als jeder Programmierer. Und halte ich für Unsinn.
Wenn man einfach überall alles mit async macht eine Heuristik entscheiden lässt, ist mMn. die Wahrscheinlichkeit hoch, dass das Programm das rauskommt schlechter performt, als ein Programm eines halbwegs fähigen Programmierers der keinen "Compiler-Support" für diese Unterscheidung bekommt.D.h. nicht dass Compiler-Support grundsätzlich abzulehnen ist. Dinge, von denen der Compiler klar beweisen kann, dass sie immer besser laufen wenn sie einfach synchron ausgeführt werden, darf er gerne synchron machen. Alles andere muss er mMn. aber in Ruhe lassen. Und das bedeutet eben dass man sich trotzdem selbst Gedanken darüber machen muss.
-
async benutzt keinen Threadpool sondern immer frische Threads? WTF?
-
omfg schrieb:
async benutzt keinen Threadpool sondern immer frische Threads? WTF?
Nein, obwohl der Standard AFAIK eben diese problematischen Passagen enthält, die IMO streng genommen nur frische Threads wirklich erfüllen.
-
Was ist das Problem am Flag
std::launch::async
per se? So wie ich dich, hustbaer, verstehe, schränkt es die Implementierung durch die erzwungene Asynchronität zu stark ein. Aber gibt es nicht Situationen, in denen man das gerade will?Nicht zuletzt, weil der Standard bei
async|deferred
auch konservative Strategien wie "immerdeferred
" (aktuell beim g++) zulässt. Ist zwar eine Frage von QoI, aber im Endeffekt machen solche Implementierungenasync()
nutzlos und zwingen einen, wieder selbst Threads zu verwalten...
-
Nexus schrieb:
Was ist das Problem am Flag
std::launch::async
per se? So wie ich dich, hustbaer, verstehe, schränkt es die Implementierung durch die erzwungene Asynchronität zu stark ein. Aber gibt es nicht Situationen, in denen man das gerade will?Die erzwungene Asynchronität ist gut, aber die Vorgabe "eigener Thread" finde ich zu einengend. Gibt ja wie schon erwähnt viele Möglichkeiten Tasks asynchron ausführen zu lassen, wobei nicht überall jeder Task nen eigenen Thread bekommt.
Aber wurst. Das wirs sicher noch nachgebessert. Würde mich wundern wenn das auf länger so bleibt.