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

Fibers und Thread-Pools

Beitrag von Schrompf »

[Abgespalten aus dem Jammer-Thread]

PAGEFAULT_IN_NONPAGED_AREA (win32kfull.sys)

Der war neu für mich. Ist eigentlich kein wirklicher Jammergrund, weil der durch einen winzigen Fehler in meiner Meshgenerierung entstand, wodurch ich irgendwas >64k Fibers mit je 128kb Stack angelegt habe. Beim ersten Mal hab ich noch ganz banal nullptr vom letzten new zurückbekommen. Und weil der Task Manager aus irgendwelchen Gründen bloß 5GB Speicher angab, wurde ich neugierig. Es gab irgendwo diese Ressourcen-Ansicht, mit der man den kompletten Committed Heap sehen konnte, aber den hab ich so schnell nicht gefunden. Und beim erneuten Starten dann mein erster Bluescreen wegen Speichermangel.

Eigentlich freu ich mich gerade viel zu sehr über die Parallelisierung und das ganze Fiber-System, als dass ich hier jammern wollen würde. Aber ich wollte zumindest anekdotisch davon erzählen.
Zuletzt geändert von Schrompf am 01.03.2016, 22:38, insgesamt 2-mal geändert.
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: Jammer-Thread

Beitrag von dot »

Was genau machst du eigentlich mit Fibers? xD
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: Jammer-Thread

Beitrag von Schrompf »

Alles :-) Ich wollte Dir auf die Schnelle die Präsentation von DICE zur Frostbite-Engine raussuchen, wo ich die Idee der expliziten Formulierung der Datenabhängigkeiten mittels Fibers her habe. Leider lässt mich mein GoogleFu gerade im Stich.

Aktuell parallelisiere ich damit als ersten Schritt die Meshgenerierung. Wenn die Kamera still steht, rendert alles mit 60+fps. Aber sobald die Kamera bewegt wird, ruckelt und hackt alles, weil da bis zu mehreren Sekunden Pause drin sind, bis die paar dutzend neuen Meshes berechnet wurden. Und es erscheint verlockend, erstmal mit den alten Meshes weiter zu rendern, bis die neuen Detailstufen da sind.
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: Jammer-Thread

Beitrag von dot »

Schrompf hat geschrieben:Alles :-) Ich wollte Dir auf die Schnelle die Präsentation von DICE zur Frostbite-Engine raussuchen, wo ich die Idee der expliziten Formulierung der Datenabhängigkeiten mittels Fibers her habe.
Das scheint mir aber doch ein wenig viel Overhead zu sein, wieso nicht einfach einen normalen Thread Pool (in Windows versteckt sich ein extrem cooler, eingebauter Thread Pool Mechanismus hinter I/O Completion Ports; Datenabhängigkeiten – zumindest der Natur wie man sie normalerweise hat – lassen sich sehr einfach modellieren, indem abhängige Tasks erst von den ihre Inputdaten produzierenden parent Tasks gespawned werden)!?

Edit: Ok, ich nehme an es gibt eine Fiber pro "Task" und das ganze basiert darauf, dass in jedem Frame die selben Tasks wieder laufen müssen und dann gibt es einen Thread Pool der sich die nächste Fiber, die ready ist, schnappt!? Interessante Idee...
kristof
Beiträge: 91
Registriert: 19.01.2009, 13:05

Re: Jammer-Thread

Beitrag von kristof »

Ich helfe mal mit dem Link zum Talk aus ;)

http://www.gdcvault.com/play/1022186/Pa ... Dog-Engine

Ich hab das auch mal prototypisch implementiert. Auch das mit dem Pipelining was er später noch erwähnt geht dann sehr einfach umzusetzen. Leider hab ich das Ganze noch nie wirklich unter einer realistischen Last gesehen. Bin gespannt was Schrompf noch so berichtet :)
Benutzeravatar
Krishty
Establishment
Beiträge: 8229
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Jammer-Thread

Beitrag von Krishty »

Würde auch zum Windows Thread Pool greifen; aus dem banalen Grund, dass er sowieso da ist – üblicherweise füttern D3D oder der GPU-Treiber nämlich schon Aufgaben rein.
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 »

Hm. Gute Ideen, denke ich - ich habe von denen vorher schlicht nichts gehört. Aber ich wollte halt eine systemunabhängie Lösung. Und Boost.Context bietet eine solche, auch wenn man so Kleinigkeiten wie "Stack wächst in negative Richtung" wissen muss und bei der Übergabe des Stack-Pointers selbst beachten muss.

Mein aktuelles System sieht so aus:

Für 8 Hardware-Threads:
8 Threads für schnelle parallele Jobs, die auf jeden Fall für das aktuelle Frames gebraucht werden
4 Thread für längerlaufende Jobs, die auch mal über das aktuelle Frame hinaus laufen dürfen
1 Thread für IO-Stuff, der primär auf die Platte wartet
1 Queue für Jobs, die sowieso im Hauptthread laufen müssen

Jede dieser Thread-Gruppen hängt an jeweils einer spezialisierten Queue mit beigeordneter condition_variable, auf der die Threads warten, falls die Queue gerade leer ist. Und das Coole daran ist, das man halt Fibers mit eigenem Stackframe einrichten und zu beliebigen Punkten auch wieder verlassen kann. Es gibt Leute, die damit Spiellogik machen: eine permanent durchlaufende Work()-Funktion, die jedesmal verlassen und im nächsten Frame neu betreten wird. Ich dagegen benutze das, um den aktuellen Ausführungsstrang zu verlassen, wenn ich auf die Fertigstellung einer Reihe anderer Jobs oder auch nur auf die Verfügbarkeit eines Mutex warte. Mutex oder Jobs haben dann so ne Art "Ich warte auf Dich"-Liste und packen die Wartenden einfach zur Wiedervorlage in die passende Queue, von wo aus sich der nächste Worker sie dann weiter ausführt.

Damit kann man halt sehr schön die Datenabhängigkeiten explizit ausformulieren: tu das, das und diese Reihe hier, und ich warte so lange. Nur halt dass der Thread nicht wirklich schläft, sondern sich währenddessen was Anderes zu tun sucht.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
xq
Establishment
Beiträge: 1581
Registriert: 07.10.2012, 14:56
Alter Benutzername: MasterQ32
Echter Name: Felix Queißner
Wohnort: Stuttgart & Region
Kontaktdaten:

Re: Fibers und Thread-Pools

Beitrag von xq »

Es gibt Leute, die damit Spiellogik machen: eine permanent durchlaufende Work()-Funktion, die jedesmal verlassen und im nächsten Frame neu betreten wird.
Gamestudio macht das so, die haben eine Funktion wait(n) wobei bei n > 0 eine gewisse Menge an Frames gewartet wird und bei n < 0 eine gewissen Anzahl an Sekunden. Bei n=0 passiert undefined behaviour, was zum fixen von bugs verwendet werden kann :P

Es ist meiner Meinung nach eine ziemlich coole Alternative zu dem klassischen GameLogic-Ansatz, aber wenn man damit aufwächst, ist es relativ schwer, davon loszulassen.

Wäre cool, wenn du deine Fiber-Lösung veröffentlichst, mich würde das durchaus interessieren, damit mal was zu bauen ;)

Grüße
Felix
War mal MasterQ32, findet den Namen aber mittlerweile ziemlich albern…

Programmiert viel in ⚡️Zig⚡️ und nervt Leute damit.
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 »

Kann ich gerne machen, aber ich muss euch natürlich wieder vor Augenkrebs-Gefahr warnen, weil ich im Privaten immernoch auf Deutsch programmiere.

Der Nachteil der Work()-Funktion mit Yield() zwischendurch ist allerdings, dass ein solcher lokaler State unmöglich in Spielstände oder sowas geschrieben werden kann. Als ich das letzte Mal eine Skriptsprache selbst geschrieben habe, hatte ich auch so eine Wait()-Funktion eingebaut, mit der man aber nur Frames warten konnte. Da die Framerate aber auf nervösen 20fps festgenagelt war, war das ok. Und man konnte halt die aktuelle Mission nur neustarten, sonst nix. Obwohl der Teil albern war, weil die Skripte ja in einer Mini-VM liefen, deren Zustand ich stress- und verlustfrei serialisieren könnte.
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:Mein aktuelles System sieht so aus:

Für 8 Hardware-Threads:
8 Threads für schnelle parallele Jobs, die auf jeden Fall für das aktuelle Frames gebraucht werden
4 Thread für längerlaufende Jobs, die auch mal über das aktuelle Frame hinaus laufen dürfen
1 Thread für IO-Stuff, der primär auf die Platte wartet
1 Queue für Jobs, die sowieso im Hauptthread laufen müssen

Jede dieser Thread-Gruppen hängt an jeweils einer spezialisierten Queue mit beigeordneter condition_variable, auf der die Threads warten, falls die Queue gerade leer ist. Und das Coole daran ist, das man halt Fibers mit eigenem Stackframe einrichten und zu beliebigen Punkten auch wieder verlassen kann. Es gibt Leute, die damit Spiellogik machen: eine permanent durchlaufende Work()-Funktion, die jedesmal verlassen und im nächsten Frame neu betreten wird. Ich dagegen benutze das, um den aktuellen Ausführungsstrang zu verlassen, wenn ich auf die Fertigstellung einer Reihe anderer Jobs oder auch nur auf die Verfügbarkeit eines Mutex warte. Mutex oder Jobs haben dann so ne Art "Ich warte auf Dich"-Liste und packen die Wartenden einfach zur Wiedervorlage in die passende Queue, von wo aus sich der nächste Worker sie dann weiter ausführt.
Du hast also effektiv einen OS Scheduler im OS Scheduler gebaut... :P

Insbesondere da du trotz all dem ja immer noch eine Work Queue brauchst, frag ich mich doch sehr, ob das – beispielsweise in Anbetracht des zusätzlichen Fiber Context Switch Overhead – wirklich effizienter ist als ein einfacher Thread Pool. Ich würde mir unbedingt mal I/O Completion Ports anschaun, die sind mitunter eines der absolut coolsten Features des Windows Kernel...
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 »

Müsste schon.

Ein Thread Switch sind ein paar tausend Takte, weswegen man da halt in gröberer Granularität arbeitet. Gute Sache für ne GUI-Anwendung mit rechenintensiven Aktionen, z.B. ein Malprogramm.

Ein Fiber Switch sind ein paar Dutzend Takte, weswegen man da in feinerer Granularität arbeitet. Prima Sache für Spiele, die in einem Frame ein paar hundert Jobs zu erledigen haben, die teilweise parallel arbeiten können, aber alle bis zum Frame-Ende fertig sein müssen.

Ob es effizienter ist, ist die andere Frage. Ich bin sicher, ich habe beim Bauen des Thread Pools und speziell der Jobverteilung irgendwelche Anfängerfehler gemacht. Das wird sich dann herausstellen.
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:Müsste schon.

Ein Thread Switch sind ein paar tausend Takte, weswegen man da halt in gröberer Granularität arbeitet. Gute Sache für ne GUI-Anwendung mit rechenintensiven Aktionen, z.B. ein Malprogramm.
Mit I/O Completion Ports hast du aber nur Threadswitches wenn der Thread blocked oder das Quota abläuft (also gleich wie mit den Threads in denen deine Fibers laufen), nicht zwischen Arbeitspaketen, deine Work Queue ist direkt im OS implementiert und du bekommst sogar noch gratis dazu, dass ein anderer Thread des Pool losgelassen wird sobald ein Thread auf I/O blocked, damit ja keine CPU Cycles unnötig verschwendet werden... ;)
Schrompf hat geschrieben:Ein Fiber Switch sind ein paar Dutzend Takte, weswegen man da in feinerer Granularität arbeitet. Prima Sache für Spiele, die in einem Frame ein paar hundert Jobs zu erledigen haben, die teilweise parallel arbeiten können, aber alle bis zum Frame-Ende fertig sein müssen.
"Ein paar Dutzend" ist wohl recht optimistisch und der Cache ist nach einem Fiber Switch genauso kalt, was vermutlich overall viel mehr ausmacht als ein paar tausend Takte... ;)

Ich hatte noch nie mit so einem System zu tun, aber rein nach der Beschreibung hier erscheint mir das alles immens kompliziert für etwas, das am Ende vermutlich nicht wirklich was bringt, vor allem wenn man bedenkt, dass du beim Coden der Fibers furchtbar aufpassen musst, dass du ja keine Library/API Funktionen oder sonstwas aufrufst, die blocken könnten, sondern alles async sein muss, damit du dann explizit switchen kannst und so, weil du sonst keinen Vorteil von den Fibers haben wirst...
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 stimme Dir zu, dass IO Completion Ports durchaus schmackhaft klingen. Geht halt nur nicht, weil nur Windows. Punkt. Der Rest der Diskussion ist dann eh müßig, aber dazu fehlt mir auch die Erfahrung mit dem Ding unter echter Last. Bisher ist es halt sehr angenehm, so seine Aufgaben zu formulieren. Blockierende OS-Funktionen spielen damit natürlich nicht mit, alles andere dagegen sehr gut. Und der Cache ist bei beiden Kontext-Wechseln arschkalt, das ist also kein Argument.

Dazu kommt in meinem Fall noch diese kleine Besonderheit des "Seriellen Jobs", die in einer speziellen Queue landen und portionsweise manuell im Hauptthread ausgelöst werden. Da packt man halt Input oder DX9-Calls rein, die halt zwingend aus dem Hauptthread kommen müssen. Der Renderer soll z.B. auf diese Art seine ganzen Arbeiten und Vorbereitungen parallel machen und dann am Ende, wenn er jeweils genug Daten für einen Renderpass zusammenhat, den Hauptthread-Job auslösen, der das Ganze an DirectX wichtelt. Ob sich die Mühe lohnt, wird sich wieder erst in den nächsten Wochen zeigen, aber es ist ein Detail, was OS-interne Lösungen rein prinzipiell so nicht bieten können.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
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 »

Klingt tatsächlich enorm komplex, aber wenn er plattformübergreifend gleiches Verhalten braucht, ist boost vielleicht wirklich der bessere Anfang.

Bzgl. Fibers: Windows’ Thread Pool nutzt intern ebenso Fibers, und jede Thread-Pool-Implementierung muss was Ähnliches tun um Rekursion und Abhängigkeiten ordentlich verwalten zu können.

Demnach wäre meine Strategie eher: Boost nach Code durchsuchen, der intern auf Windows’ Thread Pool zugreift. Gucken, ob das umgebende Interface tauglich für dich ist. Dann kannst du relativ sicher sein, dass das Verhalten auf anderen Plattformen ähnlich optimal ist; die Schnittstelle plattformübergreifend; und dass du selber nicht so viel Systemprogrammierung betreiben musst.
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 »

Auch eine Herangehensweise. Die Boost-Doku dazu besagt, dass es alternativ auch einen Build gibt, der intern die Windows-Fiber-API benutzt. Weiß nicht, was genau sie damit meinen - evtl. meinen die auch nur die alte SwitchThreadToFiber()-Funktion. Bezweifle ich aber, das ergäbe in dem Kontext keinen Sinn.

Mein Problem ist halt: Boost.Context ist ein minimales kleines Ding, was ich sehr angenehm finde. Boost bietet (vom selben Author) auch eine höhere Abstraktion an, die sich dann Boost.Coroutine nennt. Aber das ist dann wieder so ein übler Template-Krieg, der üblicherweise der Grund dafür ist, dass keiner wirklich Boost mag.
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: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? ;)
Antworten