C++ und moderne Parallelisierung unvereinbar?
-
cooky451 schrieb:
Ein quasi immutable gibt's doch? (const)
Ich müsste nochmal darüber nachdenken, aber dürfte der Compiler mit immutable nicht noch mehr optimieren dürfen? (Achtung Spekulation:) sequential consistency, cache coherence, etc. sind ja recht aufwendig sicherzustellen, wenn die Daten bei jedem Funktionsaufruf geändert werden dürfen.
-
shreadder schrieb:
Ich würde dem so zustimmen und behaupte, C++ ist für Rechner mit >32 Kernen kaum noch geeignet. Ab dann ist mehr Abstraktion von Seiteneffekten zwingend notwendig und das ist in C++ nicht sinnvoll umsetzbar. Oder doch?
Ich verstehe den ersten Satz nicht. In dem Post ging es ja um Task-Based Parallelism und dass C++11 nur Thread-bases parallelism erlaubt. Ich sehe nicht, wo da die Anzahl der Kerne reinspielt. Wenn man viele Tasks hat und nur wenige Kerne sollten bei dem Thread-Modell die Anzahl der Probleme abnehmen, wenn man mehr Kerne an board holt, eben weil dann das Verhältnis Tasks<->Threads besser stimmt.
Was natürlich fehlt ist bei vielen Threads das "wait for any future to finish". Ich bin mir aber nicht sicher, ob da wirklich alle Hoffnung verloren ist, oder ob man da nicht eventuell noch nachträglich einen future_container reinbasteln kann.
Den Rest der Argumentation teil ich nicht. Das list sich mehr nach: "Weil Haskell restriktiver ist, stellen sich bim Threading viele Probleme nicht und daher ist Threading in Haskell geiler". Die Meinung kann man teilen, aber imho ist das nicht viel anders als in einer C++ Threading bibliothek zu sagen: "Funktionen dürfen keine Seiteneffekte haben.". Der einzige Unterschied ist, dass Haskell dieses garantiert. Das ist ein wichtiger Unterschied aber nicht game-changing, weil man dies auch durch einen Contract lösen kann.
-
otze schrieb:
Den Rest der Argumentation teil ich nicht. Das list sich mehr nach: "Weil Haskell restriktiver ist, stellen sich bim Threading viele Probleme nicht und daher ist Threading in Haskell geiler". Die Meinung kann man teilen, aber imho ist das nicht viel anders als in einer C++ Threading bibliothek zu sagen: "Funktionen dürfen keine Seiteneffekte haben.". Der einzige Unterschied ist, dass Haskell dieses garantiert. Das ist ein wichtiger Unterschied aber nicht game-changing, weil man dies auch durch einen Contract lösen kann.
Da würde ich dir prinzipiell zustimmen. Ich würde aber schon sagen: Wenn man weiß, dass eine Funktion keine Seiteneffekte haben darf, wäre es schön, dem Compiler sagen zu können, dass er das sicherstellen soll, ähnlich wie const bei einer Methode.
-
shreadder schrieb:
Das von Hand zu machen ist schlimmer als OOP in C.
Na ja ich weiß nicht, Kommunikation zwischen Threads ist im Normalfall doch relativ dünn, ich kann nicht beurteilen wie das in high-performance Servern aussieht, aber generell scheint mir das kein großes Problem zu sein.
shreadder schrieb:
Ich müsste nochmal darüber nachdenken, aber dürfte der Compiler mit immutable nicht noch mehr optimieren dürfen? (Achtung Spekulation:) sequential consistency, cache coherence, etc. sind ja recht aufwendig sicherzustellen, wenn die Daten bei jedem Funktionsaufruf geändert werden dürfen.
Nun, das schreiben in const-qualifizierte Objekte ist UB, insofern kann der Compiler hier gleich optimieren. Ein Problem ist eher, dass eine const& nicht zwangsläufig auch auf eine Konstante zeigen muss.
object o; foo(o); // takes const object& bar(o); // did o change?Wenn man jetzt davon ausgeht dass bar(o), foo(o) performanter wäre, und weiter davon ausgeht dass foo nicht durchsichtig ist, dann könnte das uU eine Optimierung verhindern. Aber, diese Kombination ist schon sehr unwahrscheinlich.
Doofer istvoid foo(object& a, const object& b) { for (stuff) { a.bar(); // Could change b! b.bar(); } }Weil man da vielleicht Sachen wieder aus dem Speicher laden muss, anstatt alles in Registern zu lassen. Den Fall wird es schon öfter geben (Erschreckend oft, eigentlich. Aber zumindest wird das in den meisten Fällen wohl geinlined, dann ist es egal.), aber das ist eher ein generelles aliasing Problem und hat nur indirekt etwas mit Threading zu tun. Nichts davon ließe sich nicht prinzipiell innerhalb von C++ fixen, insofern sehe ich da auch nicht den Weltuntergang wie der Autor des Artikels.
-
otze schrieb:
Die Meinung kann man teilen, aber imho ist das nicht viel anders als in einer C++ Threading bibliothek zu sagen: "Funktionen dürfen keine Seiteneffekte haben.".
Du verstehst nicht. Es geht nicht nur um pure.
Es geht darum, dass es in Haskell eine Abstraktion für Seiteneffekte aller Art gibt.Ich kann eine Monade schreiben, die es nur erlaubt, irgendwas in eine Queue zu pushen. Und alles was Funktionen machen können, die diese Monade bekommen ist etwas in eine Queue zu pushen. Was dabei passiert ist Sache der Monade.
Eben gerade das ermöglicht STM und richtige Tasks.
In C++ ist es gar nicht mal möglich, Tasks von einem Thread in einen anderen zu verschieben, wegen thread_local (was in eigentlich Quatsch ist, wenn wir von Tasks reden).
-
Microsoft's std::mutex is built on top the Concurrency Runtime. Which is a complete rewrite of the operating system primitives, emphasizing cooperative instead of pre-emptive threading.
-
shreadder schrieb:
wegen thread_local (was in eigentlich Quatsch ist, wenn wir von Tasks reden).
Ja, aber genau an dem Punkt geht deine und die Argumentation des Textes an dem Problem vorbei. C++ definiert nur Thread-Parallelismus und keinen Task Parallelismus. Jetzt zu argumentieren, dass thread_local probleme macht, wenn man tasks machen will ist ziemlich blöd, weil das gar nicht das Ziel der _Threading_ Bibliothek ist. Man kann sich darüber streiten ob nun Threads oder Tasks die bessere Abstraktion sind oder ob eventuell tasks mithilfe von Threads als weiterer Abstraktionslayer implementiert werden können (und damit meine ich nicht, wie der Artikel versucht, die C++-Threading Bibliothek anders zu implementieren, sondern eine Task Bibliothek auf der Threading-Bibliothek aufzubauen), aber das alles sind Nebenkriegsschauplätze.
Und was die Monaden angeht: ja stimmt, aber das sagt der Artikl nicht, der nennt explizit pure als Argument. Monade kommt exakt 0 mal vor. Threading in funktionalen Sprachen ist nebenbei auch kein Selbstläufer. Ein Kollege von mir arbeitet an einem Forschungsprojekt(70m Luftlinie von meinem Büro) wo es um parallelisierung von Haskellcode geht (und nein, nicht auto-parallelisierung). Aus den theoretischen Vorteilen praktische zu machen ist eben auch nicht so einfach.
-
shreadder schrieb:
...
Evtl vorher mal selber Struppis Bibel lesen?
-
otze schrieb:
Man kann sich darüber streiten ob nun Threads oder Tasks die bessere Abstraktion sind oder ob eventuell tasks mithilfe von Threads als weiterer Abstraktionslayer implementiert werden können [...] aber das alles sind Nebenkriegsschauplätze.
Man sieht ja wohin das führt:
Regel 1: Verwende kein thread_local
Regel 2: Verwende nicht direkt thread/mutex/...
Regel 3: Verwende kein async/future, weil broken
Regel 4: Schreibe nicht in nicht-task-lokale Variablen
Regel 5: Rufe keine Funktion auf, die in nicht-lokale-Variablen schreibt (da fängts schon an)
Regel 6: vermeide const in Tasks, mach Objekte entweder const-only (immutable) oder mutable-only (d.h. ohne const Zugriff), da sonst const != konstant
...Also eine typische C++-Verbotsrichtline.
Aus den theoretischen Vorteilen praktische zu machen ist eben auch nicht so einfach.
Parallelisierung war noch nie einfach, aber C++ kommt da an eine Grenze, wo selbst einfache Task-Modelle nicht vernünftig (ohne Garantien und nur mit Verboten) umsetzbar sind.
-
Was du uns also eigentlich sagen willst ist nicht, dass C++ irgendein besonderes fundamentales Problem mit Threading hätte, sondern dass du lieber in einer Sprache arbeiten würdest in der deine "Regeln" schon integriert sind?
-
cooky451 schrieb:
Was du uns also eigentlich sagen willst ist nicht, dass C++ irgendein besonderes fundamentales Problem mit Threading hätte, sondern dass du lieber in einer Sprache arbeiten würdest in der deine "Regeln" schon integriert sind?
Es ist wie mit C und OOP/Templates: Prinzipiell machbar, aber ohne Sprachunterstützung einfach unpraktikabel.
-
shreadder schrieb:
cooky451 schrieb:
Was du uns also eigentlich sagen willst ist nicht, dass C++ irgendein besonderes fundamentales Problem mit Threading hätte, sondern dass du lieber in einer Sprache arbeiten würdest in der deine "Regeln" schon integriert sind?
Es ist wie mit C und OOP/Templates: Prinzipiell machbar, aber ohne Sprachunterstützung einfach unpraktikabel.
Ich mach's mal wie im Projekte-Forum.
Um als Anti-C++-Troll qualifizierte Antwort im C++-Forum zu bekommen, musst Du einen angemessenen Stundenlohn anbieten. Wir lösen Dein Problem, wenn Du bereit bist, zu zahlen.
-
Ich wiederhole meine Frage von vorhin: Wie würde STM in C++ aussehen?
-
shreadder schrieb:
Ich wiederhole meine Frage von vorhin: Wie würde STM in C++ aussehen?
Wieviel? Für 100€ sage ich Dir, daß Tasks gar nicht Threads sind.
-
shreadder schrieb:
Man sieht ja wohin das führt:
Das ist Bullshit. Es bedeutet nur: benutze keine Threadingbibliotek wenn du Tasking brauchst. Du benutzt auch nicht asio wenn dein Programm nur serielle i/o braucht(wobei du dir das ja alles mit strand zurechthacken könntest und dich dann darüber beschweren kannst dass io_service::post nicht synchron ist.).
Regel 3: Verwende kein async/future, weil broken
Aus dem Artikel sowie den beiden verlinkten geht nicht hervor, dass sie broken sind.
Zitat : "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. If you expected more, read on.
Don’t get me wrong, std::async combines several useful concurrency concepts into a nice package: It provides a std::future for the return value, and hides the std::promise side of the future. It also provides options to run a task synchronously. (See the Appendix for a short refresher.)".
Der Artikel über futures sagte auch nicht, dass futures broken sind, sondern dass ein (wichtiges) Feature fehlt.
Regel 4: Schreibe nicht in nicht-task-lokale Variablen
Regel 5: Rufe keine Funktion auf, die in nicht-lokale-Variablen schreibt (da fängts schon an)Äquivalent zu: "hab keine Seiteneffekte". Sehe ich als nicht problematisch an.
Regel 6: vermeide const in Tasks, mach Objekte entweder const-only (immutable) oder mutable-only (d.h. ohne const Zugriff), da sonst const != konstant
[volkard]Quatsch[/volkard]
So wird ein Schuh draus: Funktionsargumente dürfen nicht aliasen.Parallelisierung war noch nie einfach, aber C++ kommt da an eine Grenze, wo selbst einfache Task-Modelle nicht vernünftig (ohne Garantien und nur mit Verboten) umsetzbar sind.
Man muss da aber den Startpunkt betrachten: Selbst mit allen von dir angebrachten Verboten ist C++ noch immer weniger bschränkt als Haskell. Sieht man bereits bei Regel 6, da würde bei Haskell stehen: Funktionsargumente sind immutable.
-
otze schrieb:
... aber imho ist das nicht viel anders als in einer C++ Threading bibliothek zu sagen: "Funktionen dürfen keine Seiteneffekte haben.". Der einzige Unterschied ist, dass Haskell dieses garantiert. Das ist ein wichtiger Unterschied aber nicht game-changing, weil man dies auch durch einen Contract lösen kann.
Kann man nicht. Ich beschäftige mich auch gerade mit dem Thema und das hier passt ziemlich gut: Threads Cannot Be Implemented As a Library
So richtig blicke ich aber noch nicht durch.
Ich meine man müsste Anweisungen anbieten, die das Memory Model festlegen. Der Compiler kann es nämlich nicht können. Der Grund ist, dass man erst richtig Performance gewinnt wenn man Data races zulässt und trotzdem das Richtige raus kommt. Sequential consistency löst zwar die Data races, entfernt aber auch den Performance-Gewinn und ist daher inakzeptabel.
-
Wäre mir neu, dass man map-reduce nicht gut implementieren könnte :).
-
Haskell ist im Bereich von paralleler Programmierung sehr weit. Zum einen, weil sie aus den Erfahrungen anderer Programmiersprachen wie Erlang gelernt haben, zum anderen weil sich Mathematiker mit dem Problem befassen. Weiterhin hat sie nicht den Anspruch low-level zu sein.
C++ noch immer weniger bschränkt als Haskell. Sieht man bereits bei Regel 6, da würde bei Haskell stehen: Funktionsargumente sind immutable.
Nun, Haskell loest die Probleme auf andere Art und Weise, C++ ist deswegen nicht weniger eingeschraenkt.
-
shreadder schrieb:
Aufbauend auf diesem Blogpost von einem bekannten C++-Guru: http://bartoszmilewski.com/2013/09/19/edward-chands/
Den einen oder anderen seiner Blogartikel habe ich wohl gelesen, auch die zwei, die er hier im Abschnitt zur Nebenläufigkeit referenzierte ("broken promises", "task based parallelism"). Aber irgendwie finde ich davon kaum etwas überzeugend. Seine Seite flog dann irgendwann aus meinem Feed-Reader raus.
Bei diesem neuen Artikel werde ich das Gefühl nicht los, als würde er übertreiben, als würde er hier und da Äpfel mit Birnen vergleichen. Z.B. hier:
D at least has the notion of deep constness [...] But C++ doesn’t support any of this [...]
Was habe ich daran auszusetzen? Er erwähnt "deep constness" als sei das ein Feature, welches in C++ fehle. Dabei ist "deep constness" in D der Versuch, Komposition trotz aufgezwungene Indirektion zu emulieren. Komposition wird in C++ aber direkt unterstützt. In C++ haben wir diese drei Objekt<->Objekt beziehungen:
- Objekt A betitzt Objekt B. Objekt B ist Subobjekt von A (ein Teil davon). Hier verhält sich const genauso, wie man es will.
- Objekt A kennt Objekt B über einen Zeiger. B ist nicht Teil von A. Warum sollte sich die constness von A an B übertragen? Denken wir zB. an Iteratoren.
- Objekt A besitzt Objekt B, aber nur die Adresse von B ist Teil von A. Das kann nützlich sein (B könnte optional oder polymorph sein) Hier muss man eh Hand anlegen: Kopierkonstruktor/Zuweisungsoperator deaktivieren oder selbst implementieren, Destruktor schreiben. Da tut es zwecks Kapselung auch nicht weh, wenn man einen Accessor anbietet, der sich um Constness kümmern (wie z.b. operator[] bei std::vector).
"deep constness" bei Zeigern ist eher unnütz, wenn man sie einfach Kopieren kann:
struct foo { deep_const_ptr<int> ptr; }; void foo(const foo& f) { // *(f.ptr) ist ein const int Lvalue foo g = f; *(g.ptr) = 42; // oops! das geht ja sogar! }Sorry, ich werde das Gefühl nicht los, dass die, die sich über das Fehlen von "deep constness" bei C++ beklagen, nicht klar im Kopf sind. Alexandrescu hat da auch schon komische Kommentare in comp.lang.c++ gelassen, wo ich mich fragen muss, ob er "const" eigentlich verstanden hat. (Er wollte mal const-Überladungen für Konsruktoren haben).
shreadder schrieb:
std::async und std::future völlig broken,
Kannst du bitte zusammenfassen, was daran "völlig broken" ist?
shreadder schrieb:
std::mutex/std::thread/std::condition_variable sind viel zu lowlevel um sie sinnvoll einsetzen zu können
Wo ist das jetzt ein Argument für die "Unvereinbarkeit" von C++ mit "moderner Parallelisierung"? Willst Du behaupten, dass man da nicht drauf aufbauen kann?
shreadder schrieb:
Keinerlei Schutzmechanismen in der Sprache gegen Data Races u.Ä. (pure, immutable)
Das ist mir persönlich ein bisschen zu abstrakt. Am besten erklärst du mir das mal mit schönen Beispielen.
shreadder schrieb:
Daher sind STM, Tasks, etc. praktisch unmöglich sinnvoll umzusetzen un
d C++ ist ungeeignet für modernes ThreadingMit STM kenne ich mich zu wenig aus, als dass ich dazu was sagen kann. Das mit den Tasks lasse ich mir ja von dir nochmal erklären.