Seite 1 von 2

Re: Fibers und Thread-Pools

Verfasst: 02.03.2016, 12:44
von dot
Schrompf hat geschrieben:Und der Cache ist bei beiden Kontext-Wechseln arschkalt, das ist also kein Argument.
Das Argument wäre gewesen dass der kalte Cache verhältnismäßig soviel mehr weh tut, dass es praktisch keinen Unterschied machen wird, ob der Context Switch nun 100 oder 1000 Cycles gedauert hat... ;)

Re: Fibers und Thread-Pools

Verfasst: 02.03.2016, 12:46
von Krishty
Larry Osterman hat sogar vor zehn Jahren geschrieben, dass der kalte Cache seit damals zehn Jahren die Unterschiede zwischen Thread- und Fiber-Wechsel ausgeglichen hat:
https://blogs.msdn.microsoft.com/larryosterman/2005/01/05/why-does-win32-even-have-fibers/ hat geschrieben:Nowadays, the cost of blowing the cache has leveled the playing field between OS context switches and fibers – these days, you don’t get nearly the benefit from fibers that you did ten years ago.
Seit 1995 ist das also schon so. Wir sind alt, oder? :D Aber Benchmarken geht über Mutmaßen :)

Re: Fibers und Thread-Pools

Verfasst: 02.03.2016, 12:49
von Schrompf
dot hat geschrieben:
Schrompf hat geschrieben:Und der Cache ist bei beiden Kontext-Wechseln arschkalt, das ist also kein Argument.
Das Argument wäre gewesen dass der kalte Cache verhältnismäßig soviel mehr weh tut, dass es praktisch keinen Unterschied machen wird, ob der Context Switch nun 100 oder 1000 Cycles gedauert hat... ;)
Ah, verstehe. Nuja, Fiber-Wechsel kostet A+C, Threadwechsel kostet B+C. Ist die Frage, wie groß C im Vergleich zu A oder B ist. Nach dem, was ich im Kopf habe, kostet eine frische Zeile aus dem Hauptspeicher Zyklen im niedrigen Hunderter-Bereich. In der Größenordnung würden Fibers also aus Performance-Sicht noch Sinn ergeben. Wenn's eher Tausender Zyklen für eine kalte Cache-Zeile sind, wäre es irrelevant.

Re: Fibers und Thread-Pools

Verfasst: 02.03.2016, 12:55
von Schrompf
Ich habe mich mal kurz zu den I/O Completion Ports belesen. Ich verstehe ehrlich gesagt nicht, was das mit einem reinen Jobsystem zu tun hat. Das Ding ist ja explizit für IO Handles ausgelegt und funktioniert nicht ohne. Mach ich mir dann ein Dummy-File auf, auf dessen Events ich all meine Jobs verkette?

Re: Fibers und Thread-Pools

Verfasst: 02.03.2016, 12:58
von dot
Wie gesagt, ich tu hier eh nur mutmaßen, ich würde es mächtig interessant finden, wenn jemand die beiden Ansätze mal ausprobieren und vergleichen würde... ;)

@I/O Completion Ports: Ja das schaut auf den ersten Blick so aus, bis du dann die Funktion PostQueuedCompletionStatus() findest und realisierst, dass das effektiv ein verstecktes Work Distribution System für einen Thread Pool ist...die Doku strengt sich auch wirklich massiv an, auf keinen Fall auf diese Tatsache hinzuweisen... ;)
Schrompf hat geschrieben:Mach ich mir dann ein Dummy-File auf, auf dessen Events ich all meine Jobs verkette?
Nope, ein oder mehrere Files können an einen I/O Completion Port gebunden werden, muss aber nicht... ;)

Re: Fibers und Thread-Pools

Verfasst: 02.03.2016, 13:06
von Sternmull
Naja, wenn man keine IO braucht, dann kann man da auch einfach eine Queue und ein Event nehmen. Die completion ports machen Sinn wenn man auf IO-Operationen wartet von denen potentiell mehr gleichzeitig stattfinden als man Threads haben will (also z.B. 8 Threads in einem 8-Kern-Server der tausende von IO-Anfragen pro Sekunde bearbeiten soll). Wenn man in der Situation auch noch auf selbst erzeugte Ereignisse warten will, dann hilft einem PostQueuedCompletionStatus().

Re: Fibers und Thread-Pools

Verfasst: 02.03.2016, 13:09
von dot
Sternmull hat geschrieben:Naja, wenn man keine IO braucht, dann kann man da auch einfach eine Queue und ein Event nehmen.
Und wer schreibt diese high-performance, concurrent Queue? Mit einem Event allein wirst du das nicht korrekt hinbekommen und außerdem sind Events Kernel Objekte und daher mit entsprechendem Overhead verbunden... ;)

Re: Fibers und Thread-Pools

Verfasst: 02.03.2016, 15:19
von Schrompf
Hmm... ich zweifle. Ich sitze gerade am ReadWrite-Mutex. Und da stellt sich halt wie vorhergesagt heraus, dass man jedes parallele Primitive nochmal bauen muss, damit es mit den Fibers zusammenspielt. Ok, das war bekannt.

Es wird sich aber langfristig auch herausstellen, dass man wirklich gearscht ist, wenn irgendwas davon mal nicht so läuft, wie es soll. Das OS und die IDE haben inzwischen wunderschöne Mittel und Methoden, um auch parallelen Problemen auf die Pelle zu rücken. Die müsste man sich auch selbst bauen. Der VS2015-Profiler spuckt einem schön aus, auf welchem Primitives wie lange geblockt wurde. Wenn irgendein Fiber auf etwas wartet, blockt der ja nicht. Stattdessen wird der ja einfach in die Warteschlange des jeweiligen Blockers gesetzt und verschwindet ansonsten komplett aus dem System, bis der Blocker den Wartenden auf Wiedervorlage in eine der Queues packt. Falls da jemals was kaputt gehen sollte, verschwinden Fibers im Ganzen mit all ihrem Stackframe im Off. Das zu debuggen wird die Hölle.

Das Problem ist leider, dass dieser Workflow der abhängigen Jobs sehr verlockend ist. Tu das, das und das und weck mich, wenn alle Ergebnisse vorliegen. Diesen Workflow mit klassischen Threads und Synchronisation Primitives hinzubekommen liefe darauf hinaus, für jeden abhängigen Job einen neuen Thread zu spawnen oder einen aus einem Pool zu nehmen, damit man irgendwas hat, was ein Stackframe besitzt. Und all die Threads lässt man dann losrattern und überlässt es dem OS, die gegeneinander auszubalancieren, während der Auftraggeber-Thread auf irgendnem Notifier hockt und währenddessen schläft. Nichts daran begrenzt die Anzahl gleichzeitiger Threads - das könnten stressfrei auch hunderte werden.

Die I/O Completion Ports funktionieren ja nur, wenn man den oben skizzierten Workflow knickt und stattdessen den Job erst dann generiert, wenn alle Datenproduzenten fertig sind. Also quasi eine Umkehr der Auftragslage - nicht der Consumer spawnt sich die Datenproduzenten, sondern die Datenproduzenten syncen sich irgendwie und spawnen dann den Consumer. Stimmt das so?

Re: Fibers und Thread-Pools

Verfasst: 02.03.2016, 15:26
von dot
Schrompf hat geschrieben:Hmm... ich zweifle. Ich sitze gerade am ReadWrite-Mutex. Und da stellt sich halt wie vorhergesagt heraus, dass man jedes parallele Primitive nochmal bauen muss, damit es mit den Fibers zusammenspielt. Ok, das war bekannt.

[...]
Exakt und das ist einfach ein imo doch unproportional monströser Aufwand für einen zu zweifelhaften Gewinn...
Schrompf hat geschrieben:Die I/O Completion Ports funktionieren ja nur, wenn man den oben skizzierten Workflow knickt und stattdessen den Job erst dann generiert, wenn alle Datenproduzenten fertig sind. Also quasi eine Umkehr der Auftragslage - nicht der Consumer spawnt sich die Datenproduzenten, sondern die Datenproduzenten syncen sich irgendwie und spawnen dann den Consumer. Stimmt das so?
Exakt, du wirst aber feststellen, dass es in der Praxis eine völlig natürliche Art zu arbeiten ist, immerhin wissen die Producer normalerweise, was sie für wen producen, also können sie auch einfach den Job für ihren Consumer enqueuen, sobald sie fertig produced haben. Und hast du auch wirklich mehrere Producer, die parallel etwas für einen Consumer machen? Selbst das lässt sich aber einfach über einen atomic Counter oder so lösen, den jeder Producer dekrementiert und derjenige, der ihn auf 0 bringt, spanwed den Consumer...

Re: Fibers und Thread-Pools

Verfasst: 02.03.2016, 15:43
von Schrompf
dot hat geschrieben:
Schrompf hat geschrieben:Die I/O Completion Ports funktionieren ja nur, wenn man den oben skizzierten Workflow knickt und stattdessen den Job erst dann generiert, wenn alle Datenproduzenten fertig sind. Also quasi eine Umkehr der Auftragslage - nicht der Consumer spawnt sich die Datenproduzenten, sondern die Datenproduzenten syncen sich irgendwie und spawnen dann den Consumer. Stimmt das so?
Exakt, du wirst aber feststellen, dass es in der Praxis gar keine so unnatürliche Art zu arbeiten ist, immerhin wissen die Producer, für wen sie was producen, also können sie auch einfach den Job für ihren Consumer enqueuen, sobald sie fertig produced haben. Und hast du auch wirklich mehrere Producer, die parallel etwas für einen Consumer machen? Selbst das lässt sich aber einfach über einen atomic Counter oder so lösen, den jeder Producer dekrementiert und derjenige, der ihn auf 0 bringt, spanwed den Consumer...
Hm. Ist halt ein bisschen extra Aufwand, weil man halt die gemeinsamen Daten nicht bequem im lokalen Stackframe hat, sondern immer irgendwie Floating haben muss, aber das ist zugegeben ein sehr C++-spezifisches Problem. Ansonsten könnte man das so machen.

Das löst doch aber nicht das Problem der Mutexe und sonstigen Syncs? Wenn ich jetzt in einem der Jobs einen Mutex ziehe, blockt der Thread bis zur Verfügbarkeit. Das nimmt doch effektiv einen Thread raus aus dem System, bis wer auch immer fertig ist. Das ist natürlich nur dann ein Verlust, wenn noch andere Jobs ohne diese Abhängigkeit auf ihr Ausführung warten. Ist das in der Praxis ein nennenswertes Problem?

Re: Fibers und Thread-Pools

Verfasst: 02.03.2016, 15:50
von dot
Schrompf hat geschrieben:Hm. Ist halt ein bisschen extra Aufwand, weil man halt die gemeinsamen Daten nicht bequem im lokalen Stackframe hat, sondern immer irgendwie Floating haben muss, aber das ist zugegeben ein sehr C++-spezifisches Problem. Ansonsten könnte man das so machen.
Nun, nachdem die Jobs sowieso immer wieder laufen sollen, kann man die entsprechenden Daten einfach in einem Objekt (beispielsweise das Teil, das die Jobs spawned) anlegen, das sowieso irgendwo die ganze Zeit existieren muss...
Schrompf hat geschrieben:Das löst doch aber nicht das Problem der Mutexe und sonstigen Syncs? Wenn ich jetzt in einem der Jobs einen Mutex ziehe, blockt der Thread bis zur Verfügbarkeit. Das nimmt doch effektiv einen Thread raus aus dem System, bis wer auch immer fertig ist. Das ist natürlich nur dann ein Verlust, wenn noch andere Jobs ohne diese Abhängigkeit auf ihr Ausführung warten. Ist das in der Praxis ein nennenswertes Problem?
Gute Frage; ich hab auf CPU Seite eigentlich keine wirkliche Erfahrung mit so einem System. Schon seit einiger Zeit frage ich mich, ob ein I/O Completion Port auch einen anderen Thread losschickt, wenn einer auf einem Mutex blocked oder ob nur ein I/O Request zählt, das wär schade, müsste man mal ausprobieren...

Aber rein prinzipiell: Wo genau hast du in deinem Job denn vor, einen Mutex zu ziehen? ;)

Re: Fibers und Thread-Pools

Verfasst: 02.03.2016, 16:15
von Schrompf
dot hat geschrieben:Aber rein prinzipiell: Wo genau hast du in deinem Job denn vor, einen Mutex zu ziehen? ;)
Beim Zugriff auf ein Voxelmodell. Da gibt es einen Strauß Leser, die permanent gespawnt werden, Teile des Modells abklappern und daraus Meshes generieren. Die dann übrigens in einem seriellen Job am Ende des Frames hochgeladen werden, weil das ja DX9-Calls braucht. Parallel dazu kann es aber aus dem Editor heraus oder von der Spiellogik Veränderungen am Voxelmodell geben. Die dürfen nicht geschrieben werden, solange noch Leser auf dem Modell arbeiten, aber sobald jemand schreiben will, dürfen auch keine neuen Leser mehr loslegen.

Mit Fibers: kein großes Problem. Legt man die Fibers halt schlafen, bis sie den gewünschten Zugriff kriegen können.
Mit Threads: schlafen legen bis zum Zugriff? Geht, aber blockiert halt einen Slot aus dem Threadpool.

Re: Fibers und Thread-Pools

Verfasst: 02.03.2016, 16:24
von dot
Puh Ok, wenn du da eine zentrale Datenstruktur mit soviel Contention drauf hast, ist das natürlich generell etwas ungünstig. Nur mal rein prinzipiell: Wenn jemand einen Teil vom Voxelmodell, der gerade gemeshed wird, ändert, sollte der Job, der da grad dran war, nicht sowieso einfach besser abbrechen; weil das halbfertige Mesh is dann sowieso nicht up-to-date? Statt da also Reader/Writer sync zu machen, könnte man einfach lock-free den Job cancellen/restarten...

Re: Fibers und Thread-Pools

Verfasst: 02.03.2016, 16:39
von Krishty
Schrompf hat geschrieben:Mit Threads: schlafen legen bis zum Zugriff? Geht, aber blockiert halt einen Slot aus dem Threadpool.
Nein, ein vernünftiger Thread Pool (Windows dem seiner) wechselt dann einfach zum nächsten Fiber.

Re: Fibers und Thread-Pools

Verfasst: 02.03.2016, 16:40
von dot
Krishty hat geschrieben:
Schrompf hat geschrieben:Mit Threads: schlafen legen bis zum Zugriff? Geht, aber blockiert halt einen Slot aus dem Threadpool.
Nein, ein vernünftiger Thread Pool wechselt dann einfach zum nächsten Fiber.
Und wie genau funktioniert das dann mit Mutexen? ;)

Re: Fibers und Thread-Pools

Verfasst: 02.03.2016, 16:41
von Krishty
Keine Ahnung, aber da es mit File Handles und Events auch geht werden sie sich da was überlegt haben :) Schon gut, bin ja wieder weg ;)

Re: Fibers und Thread-Pools

Verfasst: 02.03.2016, 17:30
von Schrompf
dot hat geschrieben:Puh Ok, wenn du da eine zentrale Datenstruktur mit soviel Contention drauf hast, ist das natürlich generell etwas ungünstig. Nur mal rein prinzipiell: Wenn jemand einen Teil vom Voxelmodell, der gerade gemeshed wird, ändert, sollte der Job, der da grad dran war, nicht sowieso einfach besser abbrechen; weil das halbfertige Mesh is dann sowieso nicht up-to-date? Statt da also Reader/Writer sync zu machen, könnte man einfach lock-free den Job cancellen/restarten...
Zum Einen: da ist nicht viel Contention drauf. Änderungen sind selten, bestenfalls eins zwei mal pro Sekunde.

Und zum Anderen: wie bricht man den Jobs ab? Man könnte höchstens nen bool BitteHoereMalAuf; von außen setzen und müsste dann mit dem Schreibzugriff trotzdem warten, bis alle lesenden Operationen zu Ende gekommen sind. Wir reden hier ja von beliebigem C++-Code - selbst wenn das ein Thread wäre und Du den hart killst, bliebe alles an Ressourcen und Objekten unaufgeräumt. Und den Fiber kannst Du von außen gar nicht killen, weil das ja alles kooperatives Multitasking ist.

Re: Fibers und Thread-Pools

Verfasst: 02.03.2016, 18:41
von Sternmull
Ich war jetzt zu faul alles zu lesen was bisher geschrieben wurde. Aber von den letzten Beiträgen her sieht es so aus als wäre dein Problem eigentlich einfach zu lösen: Nimm SRW Locks. Damit können beliebig viele Threads gleichzeitigen lesenden Zugriff haben und eine seltenen schreib-Zugriffe scheinen ja grundsätzlich nicht das Problem zu sein. Die Lesezugriffe sollten von einem Thread-Pool bearbeitet werden und einzelne Lese-Locks sollten möglichst kurz gehalten werden damit ein Schreibzugriff auch irgendwann mal zum Zuge kommen kann.

Re: Fibers und Thread-Pools

Verfasst: 04.03.2016, 09:54
von odenter
Für die Platformunabhängigkeit wäre boost.asio vielleicht das richtige.
Unter Windows werden I/O completion ports verwendet und unter einem Linux wird epoll verwendet wenn mich nicht alles täuscht.
Mehr Infos hier http://think-async.com/Asio/

Ich habe nicht völlig verstanden was Du genau machen willst. Es gibt die Möglichkeit mit boost.asio asynchrone Logic wie synchrone zu behandeln (Stackful/Stackles Coroutines). Willst Du sowas machen?