Fibers und Thread-Pools

Design Patterns, Erklärungen zu Algorithmen, Optimierung, Softwarearchitektur
Forumsregeln
Wenn das Problem mit einer Programmiersprache direkt zusammenhängt, bitte HIER posten.
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Fibers und Thread-Pools

Beitrag 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... ;)
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Fibers und Thread-Pools

Beitrag 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 :)
Zuletzt geändert von Krishty am 02.03.2016, 12:49, insgesamt 1-mal geändert.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Fibers und Thread-Pools

Beitrag 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.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Fibers und Thread-Pools

Beitrag 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?
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Fibers und Thread-Pools

Beitrag 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... ;)
Zuletzt geändert von dot am 02.03.2016, 13:08, insgesamt 1-mal geändert.
Benutzeravatar
Sternmull
Establishment
Beiträge: 264
Registriert: 27.04.2007, 00:30
Echter Name: Til
Wohnort: Dresden

Re: Fibers und Thread-Pools

Beitrag 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().
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Fibers und Thread-Pools

Beitrag 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... ;)
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Fibers und Thread-Pools

Beitrag 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?
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Fibers und Thread-Pools

Beitrag 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...
Zuletzt geändert von dot am 02.03.2016, 15:44, insgesamt 1-mal geändert.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Fibers und Thread-Pools

Beitrag 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?
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Fibers und Thread-Pools

Beitrag 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? ;)
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Fibers und Thread-Pools

Beitrag 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.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Fibers und Thread-Pools

Beitrag 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...
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Fibers und Thread-Pools

Beitrag 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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Fibers und Thread-Pools

Beitrag 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? ;)
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Fibers und Thread-Pools

Beitrag 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 ;)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 4838
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Fibers und Thread-Pools

Beitrag 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.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
Sternmull
Establishment
Beiträge: 264
Registriert: 27.04.2007, 00:30
Echter Name: Til
Wohnort: Dresden

Re: Fibers und Thread-Pools

Beitrag 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.
odenter
Establishment
Beiträge: 207
Registriert: 26.02.2009, 11:58

Re: Fibers und Thread-Pools

Beitrag 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?
Antworten