C++ Threads

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
Jonathan
Establishment
Beiträge: 2545
Registriert: 04.08.2004, 20:06
Kontaktdaten:

C++ Threads

Beitrag von Jonathan »

Hi,
nachdem ich mich lange davor gedrückt habe, möchte ich jetzt doch einmal Threads benutzen, um Daten im Hintergrund zu laden. Da ich recht neu in der Thematik bin (nur mal ein bisschen was in Qt und Java damit gemacht) und die Thread-Bibliothek doch recht umfangreich erscheint, dachte ich mir, ich frage einfach mal hier nach.

Konkret möchte ich Texturen zur Laufzeit nachladen. Momentan lade ich ein Modell und rufe darin mehrmals die TextureLoad() Methode auf, die mir eine OpenGL Textur als Integer liefert. Jetzt möchte ich das Programm so umbauen, dass die Textur in OpenGL zwar schon erstellt wird, aber noch nicht das Bild beinhaltet. Ein Thread soll im Hintergrund die Daten laden und sich dann beim Hauptthread melden, so dass dieser dann die Daten aus dem Ram in die OpenGL-Textur schieben kann (da OpenGL glaube ich nicht Threadsicher ist). Jetzt hätte ich da eine handvoll Fragen:
- Ein Thread pro Textur, die geladen wird, oder lieber ein großer Thread, der eine Liste an Texturen abarbeitet?
- Wie kann mein Ladethread eine Funktion aus dem Hauptthread aufrufen? Oder muss der Haupthread regelmäßig nachfragen, ob der Arbeitsthread Ergebnisse hat? Wenn ja, wie würde ich das am geschicktesten machen?
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Schrompf
Moderator
Beiträge: 5047
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: C++ Threads

Beitrag von Schrompf »

Threads zu erstellen oder zu löschen ist ein gewisser Aufwand vom Betriebssystem. Ich würde also maximal soviele Arbeitsthreads erstellen, wie die CPU Kerne hat, plus evtl. langlaufende IO-Threads, die primär auf Signale vom Betriebssystem warten.

Für Deinen konkreten Anlass ist das alles aber uninteressiert. Schau Dir mal std::async() und std::future<> an. Damit bist Du bereits gut aufgestellt, und das ganze Thread-Management macht jemand anderes für Dich.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
anonym
Beiträge: 79
Registriert: 15.07.2009, 07:35
Kontaktdaten:

Re: C++ Threads

Beitrag von anonym »

Für mich hören sich die Anforderungen eher nach asynchroner Ein-/Ausgabe an, anstatt für jede solche Operation nur zum Warten einen Thread zu erzeugen. Wenn dann tatsächlich erst nach Erhalt der Daten mit minimalem Aufwand für den Hauptthread eine Ressource erzeugt werden sollte, wäre ein eigener Thread für die Erzeugung und Befüllung dieser anzudenken, um im Haupthtread nur eine kleine Updateoperation in der Ressourcenverwaltung ausführen zu müssen. Wenn aber Erzeugung und Befüllung sowieso im Hauptthread ablaufen, habe ich Zweifel, ob ein Thread zum Vermeiden einer blockierenden Leseoperation im Hauptthread gegenüber asynchroner Ein-/Ausgabe im Hauptthread Vorteile bieten würde.
Benutzeravatar
Jonathan
Establishment
Beiträge: 2545
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: C++ Threads

Beitrag von Jonathan »

Asynchrones I/O wäre evtl. auch einen Blick wert, aber ich denke, das Laden ansich wird damit nicht schneller gehen, da die CPU da eigentlich kaum etwas macht, sondern hauptsächlich Daten von der Festplatte in den RAM geschaufelt werden (naja, es werden auch JPEG-Texturen entpackt, für die Fälle würde es vermutlich schon etwas bringen).
Aber ein bisschen langfristiger gesehen ist mein Ziel auch einfach, eine Art simples Streaming zu haben, also im laufenden Spiel Daten nachladen zu können. Um mich der Thematik zu nähern, wollte ich jetzt halt Texturen erst zur Laufzeit laden (was natürlich die Konsequenz hat, dass die Szene in den ersten Sekunden ziemlich albern aussieht, aber das nehme ich für den Anfang gerne in Kauf).

async und future habe ich mir schon angesehen, das wurde auch in diversen Tutorials erwähnt. Allerdings wurde immer nur mit .get() auf den Wert zugegriffen, und das blockiert halt so lange, bis das Ergebnis da ist, was mir zunächst mal überhaupt nichts bringt. Auf die Schnelle habe ich auch nichts herausgefunden, wie ich prüfen kann, ob ein Ergebnis da ist, also Polling so zu sagen. Allerdings bin ich mir auch noch gar nicht sicher, ob das wirklich die beste Lösung ist, oder ob ich meinen Hauptthread nicht auch anders dazu bringen kann, mal eben ein paar Texturen hochzuladen (daher auch dieser Thread).
Wenn ich jetzt allerdings davon ausgehe, dass ich nur einen Lade-Thread habe, nützt mir ja ein einzelnes Ergebnis eh nichts mehr - ich müsste viel mehr bei jedem Datenblock der ankommt die Textur erstellen.

Spontan würde ich jetzt versuchen, irgendwie mit geteilten Speicherbereichen und mutexen die Threadkommunikation zu lösen, denn der Arbeitsthread muss ja neue Aufträge bekommen, bzw. irgendwann auch mal beendet werden, und der Hauptthread muss informiert werden, wann immer etwas neues geladen wurde. Man könnte in beiden Threads Polling betreiben, aber das hört sich nicht sonderlich clever an. Allerdings sehe ich auf die Schnelle auch keine Möglichkeit, den Arbeitsthread vom Hauptthread aus schlafen zu legen und später wieder aufzuwecken (was zumindest das Polling auf einer Seite lösen dürfte).
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Benutzeravatar
Schrompf
Moderator
Beiträge: 5047
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: C++ Threads

Beitrag von Schrompf »

Tja... das Thema ist nicht ganz einfach. Zuerst zu std::future - die Tutorials und Hinweise dazu im Netz sind m.E. dumm, da sie das Thema komplett verfehlen. Niemand wirft asynchron eine Aufgabe an, nur um dann den Hauptthread schlafen zu legen, bis sie fertig ist. Da ist niemandem mit geholfen. Ich vermute, die Artikelschreiber wollten sich einfach nur um die zusätzlichen Zeilen für Polling drücken. Polling geht tatsächlich, und ich halte das eher für den Standardfall. Du kannst bei get() auch einfach ein Timeout von 0 angeben, dann kommt der Aufruf sofort zurück, wenn das Ergebnis noch nicht da ist. Der eine Fall, bei dem ich das verwendet habe, nimmt aber stattdessen einen std::atomic<Ergebnis*>, der dann mittels load() regelmäßig gegen nullptr geprüft wird.

Bei den Splitterwelten habe ich das mittels TBB gelöst. Die TBB war (zumindest damals) noch nicht wirklich als allgemeine Job Queue brauchbar, weil die Jungs viel zu sehr auf datenparallele Aufgaben fixiert waren, was meiner Meinung nach ein seltener Glücksfall ist, den man ganz trivial parallelisieren kann, und nicht etwa der Standardfall. Ich habe mir damals mit tbb::thread einfach ein paar Workerthreads gebaut, die dann regelmäßig eine tbb::concurrent_queue nach Aufgaben pollen und ohne Aufgabe ein paar ms schlafen. Aufgaben waren bei mir dann von einer Basisklasse abgeleitet und hatten eine Funktion für parallele Arbeit und eine Funktion für sequentielle Arbeit, also Arbeit, die nur im Hauptthread gemacht werden kann. Ich habe dann pro Textur oder zu generierendem Shader eine Aufgabe in die Queue gepackt. Der parallele Teil wurde von einem Worker Thread gemacht, welcher die Aufgabe danach auch in eine zweite Queue für die serielle Nachbearbeitung verschoben hat. Und die Nachbereitung (Erstellen der Textur und hochladen des Contents) wurde dann im Engine-Thread gemacht. Der hatte ein Zeit-Budget pro Frame, während dem er soviele Aufgaben von der Nachbearbeitungs-Queue abgeackert hat, wie er konnte.

Es lohnt sich meiner Meinung nach, aber nur begrenzt. Das Laden der Dateien ist das Eine - bei JPEG ist das durchaus eine ordentliche Rechenaufgabe, die die Parallelisierung lohnt. Abgesehen davon rate ich von JPEG ab, weil verlustbehaftet. Aber gut. Wir haben stattdessen zumeist DDS-Dateien, weil die auch seltsame Pixelformate und CubeMaps unterstützen. Und da ist die Wartezeit auf die Festplatte der primäre Posten, und nichtmal der sonderlich groß. Der andere Teil ist das Erzeugen der Textur und das Hochladen des Contents. Und das kriegst Du nicht parallelisiert, obwohl es ein merklicher Aufwand ist. Selbst im Idealfall, dass Du die maximale PCIE-Transfer-Geschwindigkeit bekommst, braucht eine komprimierte 2048er Textur immernoch ~10ms. Und die kannst Du nicht verstecken, egal was Du tust. Ärgerlich.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: C++ Threads

Beitrag von dot »

Es hängt natürlich davon ab, was genau du machen willst, aber wenn es einfach nur darum geht, dynamisch Texturen nachzuladen, dann wäre folgendes vielleicht eine Lösung: Du machst dir einen Worker Thread, der seinen eigenen OpenGL Context hat (shared mit dem Hauptcontext). Wenn eine Textur angefordert wird, dann wird diese erstmal mit der id einer Dummy-Textur initialisiert und ein entsprechender Auftrag in die Queue des Worker Thread gepushed (der Worker kann z.B. über eine Condition Variable benachrichtigt werden, wenn es nur um Windows geht, wären auch Events oder vor allem I/O Completion Ports denkbar). Der Thread erzeugt die neue Textur und lädt sie hoch und sobald er damit fertig ist, kann er die Dummy id per atomic Exchange gegen die id der geladenen Textur tauschen. Der Renderer kann parallel dazu fröhlich weiter vor sich hin rendern und einfach immer die aktuelle id benutzen; sobald die Textur geladen ist, verwendet er dann autom. statt dem Dummy die richtige Textur. Nur mal so eine Idee, nicht ausprobiert... ;)
Niki
Establishment
Beiträge: 309
Registriert: 01.01.2013, 21:52

Re: C++ Threads

Beitrag von Niki »

Ich hab's zwar noch nicht mit Textur- und Modell-Nachladen getestet, aber ich habe für Probleme diese Art eine Callback Queue. Das grundlegende Prinzip ist sehr einfach. Die Callback Queue ist ein Array, welches Callback-Informationen enthält:

Quellobjekt: das Objekt welches die Callback Methode aufrufen will
Zielobjekt: das Objekt welches die Callback Method enthält
Zielmethode: die aufzurufende Methode
Parameter: ein Parameter mit User-Daten der an die aufzurufende Method übergeben wird
Wait-Until-Done: ein Flag das, falls erwünscht, mittels Kernel Events blocken kann bis der Callback ausgeführt wurde

Der Aufrufer der Callback schreibt innerhalb einer Critical Section neue Callback Informationen in das Array. Der Hauptthread durchläuft einmal pro Frame das Array und führt die Callbacks aus (wiederum mittels einer Critical Section). Um die Critical Section kurz zu halten erzeuge ich ein neues leeres Array für neue Callbacks, während das alte Array außerhalb der Critical Section abgearbeitet wird.

Obiges liest sich nun einfacher als es wirklich ist. Das Problem ist die Lebenszeit von Objekten (Quellobjekt, Zielobjekt, Parameter). Wenn Objekte schon tot sind wenn du Callbacks ausführst, dann rumst es schnell. Wie dieses Problem zu lösen ist hängt klar von deinem Source Code ab. Bei mir ist es relativ einfach, weil ich ein Objektsystem mit thread-sicherem Reference Counting habe.

Falls Du MacOS oder iOS kennen solltest, dann ist obiges vergleichbar mit [NSObject performSelectorOnMainThread]
Benutzeravatar
Schrompf
Moderator
Beiträge: 5047
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: C++ Threads

Beitrag von Schrompf »

Sorry, aber man merkt, dass Du eine Weile nicht an der Softwareentwickler-Front tätig warst :-) Deine Erfindung lässt sich im Ganzen durch boost::signals2 ersetzen, die ja von Haus aus bereits threadsicher sind. Alternativ auch mit einer Lockless Queue als performanter Ersatz für das Array-CriticalSection-Gebastel. Du kannst im eigenen Code natürlich tun und lassen, was Du willst, aber wenn es hier um öffentlich lesbare Tipps geht, würde ich das nicht so stehen lassen. Und std::shared_ptr neu zu erfinden ist auch nicht so weiterempfehlenswert.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Niki
Establishment
Beiträge: 309
Registriert: 01.01.2013, 21:52

Re: C++ Threads

Beitrag von Niki »

Ich benutze absichtlich kein Boost :) Weißt du, ich habe da an einem mittelgroßen 3D Projekt in einem 40 Mann Team mitgearbeitet. Compile-Zeit knapp 45 Minuten auf einem einzelnen Rechner, und etwa 20 Minuten mit anderen Rechnern im Netzwerk. Beides viel zu viel - mal abgesehen davon das es teuer ist wenn 80% des Teams mehrfach am Tag compilieren müssen.
Grund dafür war das plötzlich jemand die gesamte Character-Animation und AI-Kiste mit Hilfe von Boost implementierte. Einmal komplett zugetemplatet und die allgemeine Freude war groß. Deshalb benutze ich kein Boost.
Benutzeravatar
Schrompf
Moderator
Beiträge: 5047
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: C++ Threads

Beitrag von Schrompf »

Geschmackssache. Ich empfinde das als Ausrede. Zum Einen ist man nicht in einem 40Mann-Team, sondern primär allein, und sollte eher darauf achten, soviel fertige Arbeitsergebnisse wie möglich zu verwenden. Und zum Anderen ist Boost nicht ein Gesamtpaket, sondern viele kleine einzelne Komponenten. Und wenn die eigene Implementation auch nur annähernd die Features, Sicherheit und Performance der jeweiligen Boost-Lösung erreichen will, wird sie auch den Code-Umfang erreichen. Im Endeffekt hat man also durch eine Boost-Vermeidung null Vorteile und einiges an zusätzlicher Arbeit.

[edit] Der eigentliche Schuldige ist also eher das antiquirierte C++-Buildsystem. Dagegen gibt es Ansätze, aber es wird wohl das eine oder andere Jährchen ins Land gehen, ehe sich das weitreichend durchgesetzt hat.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Niki
Establishment
Beiträge: 309
Registriert: 01.01.2013, 21:52

Re: C++ Threads

Beitrag von Niki »

Ich finde mit der "Geschmackssache" hast du den Nagel auf den Kopf getroffen. Für dein Empfinden ist Boost die ultimative Kollektion von Tools, und da gibt es auch nichts dran auszusetzen. Wenn Boost schlecht wäre dann wäre es nicht derart populär.

Das bedeutet aber meiner Ansicht nach nicht, dass Boost die einzige gute Lösung für gewisse Probleme ist. Klar bietet Boost Vorteile, aber nimm mal mein aktuelles Projekt (ein Single-Player First-Person RPG). Dieses Projekt werde ich irgendwann mal, ob ich will oder nicht, auf andere Plattformen porten müssen die kein C++ haben. Der C++ Code den ich jetzt habe mag ohne Boost um ein vielfaches aufwendiger sein, aber er hat Elemente die ich in den zwei anderen Hauptsprachen (C# und Java) wiederfinde. Persönlich fühle ich mich dabei wohler.

Aber der Originalposter, Jonathan, schreibt nichts von Boost oder STL. Ich kann mir in meinem Kopf nicht ausmalen wie sein Source Code aufgebaut ist. Deshalb denke ich das er der einzige ist der die für ihn richtige Entscheidung treffen kann. Ich nehme an es wird Boost sein, aber wirklich wissen kann ich das nicht.
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: C++ Threads

Beitrag von dot »

Nur so aus Interesse: Auf welchen Plattformen gibt's Java und C# aber kein C++ und wieso verwendest du in dem Fall nicht gleich eine dieser beiden Sprachen!?
Niki
Establishment
Beiträge: 309
Registriert: 01.01.2013, 21:52

Re: C++ Threads

Beitrag von Niki »

Diese Plattformen findest Du hauptsächlich unter mobile Geräten. Geräte die rein Java unterstützen gehören langsam zum Alteisen. Dazu gehören zum Beispiel Nokia S40 Geräte oder Sprint Geräte. Das ist alles alter Schrott aber manchmal kann man den nicht ignorieren. Willst du zum Beispiel Brew und Sprint in Amerika unterstützen, dann kannst du dir die Geräte nicht aussuchen. Da wird eine Liste vom Carrier vorgegeben.

Ein Gerät mit nur C# ist Windows Phone 7. Ich bin mir nicht sicher, aber ich glaube das trifft auch für die XBox 360 zu, es sei denn du bist einer der ganz großen Entwickler die ein C++ SDK erhalten. Aber das weiß ich eben nicht 100%-ig, weil ich noch nie für die XBox 360 entwickelt habe.

EDIT: Ich verwende nicht direkt C# oder Java weil ich mein gutes altes C++ einfach lieb habe :D Zudem benutzt die momentan wichtigste mobile Plattform, iOS, Objective-C/C++.
Benutzeravatar
Chromanoid
Moderator
Beiträge: 4273
Registriert: 16.10.2002, 19:39
Echter Name: Christian Kulenkampff
Wohnort: Lüneburg

Re: C++ Threads

Beitrag von Chromanoid »

Bei den meisten älteren mobilen Endgeräten sieht es AFAIK mit Nützlichkeit von Threads eh ziemlich mau aus. Ich würde das eher wie Schrompf handhaben und was bereits bewährtes nutzen. Das ganze hinter einer Facade verstecken kannst Du ja trotzdem. Ich spreche hier allerdings aus der Perspektive eines Java-Entwicklers.
Zuletzt geändert von Chromanoid am 31.03.2013, 18:51, insgesamt 1-mal geändert.
Grund: zu war zuviel -.-
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: C++ Threads

Beitrag von Krishty »

Chromanoid hat geschrieben:Das ganze hinter einer Facade zu verstecken kannst Du ja trotzdem.
Genau. Kapsel es einfach weg. Da man Boost (wie auch WinAPI, COM, und den ganzen Rest) niemals direkt #includet sondern nur in einzelnen Übersetzungseinheiten, dauert das Kompilieren auch nur drei Sekunden länger, und nicht 50 Minuten. Verstecken, verstecken, verstecken (2. & 3. funktionieren sehr gut).
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
spobat
Beiträge: 86
Registriert: 13.09.2010, 00:20
Kontaktdaten:

Re: C++ Threads

Beitrag von spobat »

Schrompf hat geschrieben:Threads zu erstellen oder zu löschen ist ein gewisser Aufwand vom Betriebssystem. Ich würde also maximal soviele Arbeitsthreads erstellen, wie die CPU Kerne hat
Meinst du Prozesse?
Beim erstellen von Threads wird iirc so gut wie gar nichts gemacht. Die teilen sich immerhin ja alle den gleichen Arbeitsspeicher und haben vielleicht einen kleinen eigenen Bereich.

Trotzdem stimme ich Jonathan dabei zu, dass es fraglich ist, ob man damit wirklich die Performance erhoehen kann. Zehn verschiedene Dateien in kleinen Stuecken einzulesen dauert da vermutlich laenger als eine Datei nach der anderen mit einem ordentlichen Stream aufzufassen. Was sich lohnen koennte, sind die Berechnungen, die nach dem Laden erfolgen (gibt es sowas bei dir, oder sind die resourcen gleich nach dem laden fertig zu benutzen?) zu parallelisieren.
Benutzeravatar
Schrompf
Moderator
Beiträge: 5047
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: C++ Threads

Beitrag von Schrompf »

spobat hat geschrieben:
Schrompf hat geschrieben:Threads zu erstellen oder zu löschen ist ein gewisser Aufwand vom Betriebssystem. Ich würde also maximal soviele Arbeitsthreads erstellen, wie die CPU Kerne hat
Meinst du Prozesse?
Nein, ich meine Threads. Prozesse sind nochmal deutlich teurer, da hast Du Recht. Aber auch Threads würde ich nicht für jede Parallel-Aufgabe neu erstellen.

Jonathan wollte übrigens das Laden von JPEGs parallelisieren. Da ist schon etwas mehr Arbeit notwendig als der reine Dateizugriff. Das lohnt sich meiner Meinung nach.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
spobat
Beiträge: 86
Registriert: 13.09.2010, 00:20
Kontaktdaten:

Re: C++ Threads

Beitrag von spobat »

Ja, stimme dir da zu. Es koennte sich insofern als sinnvoll ergeben, als dass einige Threads rechnen, waehrend andere blocken. Vor dem "overhead" des Thread-erstellens haette ich eher weniger Angst, das geht idr. 10 - 100 mal so schnell wie das erstellen eines Prozesses (MOS 3rd Ed., P. 95).

Die Frage ist nur, ob sich ein Thread pro Datei lohnt, oder doch ein definiertes maximum von beispielsweise numVirtualCPUs*2 (oder aehnlich) . Ich wuerd's benchmarken. :)
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: C++ Threads

Beitrag von Krishty »

spobat hat geschrieben:Beim erstellen von Threads wird iirc so gut wie gar nichts gemacht. Die teilen sich immerhin ja alle den gleichen Arbeitsspeicher und haben vielleicht einen kleinen eigenen Bereich.
Systemaufruf. Auf OS-Seite Zuweisen und Initialisieren von Handles, internen Datenstrukturen, und Speicherraum; Reservieren und Zuweisen eines Stapelspeicherbereichs. Kontextwechsel. Auf Anwenderseite Initialisierung von thread-local Storage; Installieren von Ausnahmeverarbeitung; Initialisierung von Sicherheitsbeschreibungen; und dann noch so CRT-Kram wie Laden von Buchstabenkonvertierungstabellen.

Klar ist das so gut wie garnichts wenn ein CPU-Kern 16 Milliarden Befehle pro Sekunde abarbeitet und dir Systemaufrufe und globale Wirkungen nichts ausmachen. Sein muss es aber trotzdem nicht.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 5047
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: C++ Threads

Beitrag von Schrompf »

spobat hat geschrieben:Die Frage ist nur, ob sich ein Thread pro Datei lohnt, oder doch ein definiertes maximum von beispielsweise numVirtualCPUs*2 (oder aehnlich) . Ich wuerd's benchmarken. :)
Das ist eh immer eine gute Idee :) Als Hausnummer Kerne*2 Threads zu nehmen ist auch prima. Intel empfiehlt genau 1*Kerne Threads, aber ich hatte ja schon geschrieben, was ich davon halte.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
spobat
Beiträge: 86
Registriert: 13.09.2010, 00:20
Kontaktdaten:

Re: C++ Threads

Beitrag von spobat »

Die Idee dahinter ist, dass nicht alle Threads gleichzeitig rechnen oder gleichzeitig blocken.
Schrompf hat geschrieben:Intel empfiehlt genau 1*Kerne Threads
Hast du dazu nen link?
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: C++ Threads

Beitrag von Krishty »

Unter Windows hat man übrigens die Thread Pool API, um sich nicht selber um sowas kümmern zu müssen.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 5047
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas
Wohnort: Dresden
Kontaktdaten:

Re: C++ Threads

Beitrag von Schrompf »

spobat hat geschrieben:
Schrompf hat geschrieben:Intel empfiehlt genau 1*Kerne Threads
Hast du dazu nen link?
Keinen konkreten Link, sorry. Ich glaube, dass irgendwo in der TBB-Doku aufgeschnappt zu haben. Aber nochmal: die TBB ist nicht wirklich ein gutes Beispiel für eine Parallel Task Queue. Im Parallelisieren der Verarbeitung großer Datenmengen macht denen niemand was vor. Aber Jobs wie "Textur laden, dekodieren und in den VideoRAM hochladen" kommt in deren Planung gar nicht vor. Bzw. kam nicht vor - ich habe schon eine Weile nicht mehr nachgeschaut.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
dot
Establishment
Beiträge: 1745
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: C++ Threads

Beitrag von dot »

Es hängt von der Anwendung ab, so allgemein kann man das imo nicht sagen. Wenn man eher I/O bound ist, macht es unter gewissen Umständen evtl. Sinn, mehr Threads als Cores zu haben (unter Windows sich da vorher aber unbedingt mal I/O Completion Ports anschauen!), wenn man von Hyperthreading profitieren kann evtl. auch, ansonsten evtl. eher nicht. Ich hab jedenfalls schon beides gesehen...
Benutzeravatar
Krishty
Establishment
Beiträge: 8316
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: C++ Threads

Beitrag von Krishty »

Bei I/O-Limitierung stimme ich zu. Hyperthreading am besten garnicht anfassen, denn bei jeder noch-so-popeligen Anwendung laufen eh noch >8 andere Threads im Hintergrund mit (einer für Direct3D, einer pro Kern für den Grafiktreiber, einer für die Sound-API, Prefetching, Page Clearing, und irgendwo ist auch noch ein Browser mit YouTube-Musik offen …) die sowieso schon konkurrieren.

Man darf halt nicht vergessen, dass man durch Hyper-Threading zwar die pure Rechenleistung besser ausnutzen kann, aber schlimmstenfalls auch nur noch die halbe Cache-Menge zur Verfügung hat; darum lasst das lieber für fremdverschuldete Kümmel-Threads denn für hochleistungs-JPEG-Dekompression …
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Jonathan
Establishment
Beiträge: 2545
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: C++ Threads

Beitrag von Jonathan »

So, es funktioniert, aber es fühlt sich so hässlich an. Ich gebe meine Lösung einfach mal zur allgemeinen Diskussion frei (reduziert auf das wesentliche):

Code: Alles auswählen


struct LoadingThreadData
{
	atomic<bool> End;
	atomic<bool> Loaded;
	fipImage& Image;
	std::string& Filename;
};

struct TextureLoadingJob
{
	std::string Filename;
	unsigned int TextureIndex;
};

class ResourcesManager
{
	unsigned int GetTexture(std::string Filename);
	void PollLoadingThread();

	thread m_LoadingThread;
	LoadingThreadData m_LoadingThreadData;
	fipImage m_ThreadImage;
	std::string m_ThreadFilename;
	std::vector<TextureLoadingJob> m_ThreadJobs;
};


ResourcesManager::ResourcesManager():
m_LoadingThread(LoadingThread, std::ref(m_LoadingThreadData)),
m_LoadingThreadData(m_ThreadImage, m_ThreadFilename)
{
}

ResourcesManager::~ResourcesManager()
{
	m_LoadingThreadData.End=true;
	m_LoadingThread.join();
}

unsigned int ResourcesManager::GetTexture(std::string Filename)
{	
	//return 0;

	auto It=m_Textures.find(Filename);
	if(m_Textures.end()!=It)
	{
		return It->second;
	}

	//load the texture:
	GLuint NewTex;
	glGenTextures(1, &NewTex);
	
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	m_ThreadJobs.push_back(TextureLoadingJob(Filename, NewTex));
	PollLoadingThread();
	return m_Textures[Filename]=NewTex;
}


void ResourcesManager::PollLoadingThread()
{
	if(m_LoadingThreadData.Loaded)
	{
		auto Job=m_ThreadJobs.back();
		glBindTexture(GL_TEXTURE_2D, Job.TextureIndex);

		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_LoadingThreadData.Image.getWidth(), m_LoadingThreadData.Image.getHeight(), 0,
			GL_BGRA, GL_UNSIGNED_BYTE, m_LoadingThreadData.Image.accessPixels());
		glGenerateMipmap(GL_TEXTURE_2D);

		//delete the job
		m_ThreadJobs.pop_back();
		m_ThreadFilename="";

		//and start new one
		if(!m_ThreadJobs.empty())
			m_ThreadFilename=m_ThreadJobs.back().Filename;

		//and restart the thread
		m_LoadingThreadData.Loaded=false;
	}
	else//see, if we have a new job
	{
		if(!m_ThreadJobs.empty())
			m_ThreadFilename=m_ThreadJobs.back().Filename;
	}
}


void LoadingThread(LoadingThreadData& Data)
{
	while(!Data.End)
	{
		if(!Data.Loaded && !Data.Filename.empty())
		{
			cout << "Loading " << Data.Filename << endl;
			//Load the file
			if(!Data.Image.load(Data.Filename.c_str()))
			{
				THROW(Exception("Image could not be loaded with FreeImage") << TextureFilenameInfo(Data.Filename));
			}
			Data.Image.convertTo32Bits();

			Data.Loaded=true;
		}
		else
			boost::this_thread::sleep(boost::posix_time::milliseconds(200));
	}
}
Die Grundidee ist jetzt: Die Textur lege ich sofort an, gefüllt wird sie aber erst, wenn das Bild fertig geladen wurde. Alle Bilder werden mit TexturId in eine Liste geschrieben. Der Thread hat Referenzen auf ein Bild und den Dateinamen und weiß daher immer, was er als nächstes zu laden hat.

Naja, ich bin sehr zufrieden, dass es jetzt schon prinzipiell funktioniert, aber ich merke, dass ich im Thema Thread-Programmierung noch wenig Erfahrung habe und dementsprechend hässlich fühlt sich der Code an. Über Vorschläge, wie man das sauberer Lösen kann, freue ich mich.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
simbad
Establishment
Beiträge: 130
Registriert: 14.12.2011, 14:30

Re: C++ Threads

Beitrag von simbad »

Ich habe jetzt am morgen vielleicht nicht alles erfasst.
Du hast deinen Loader-Thread der die Arbeit macht, und dann pollst du den Status des Load Vorgangs und ob neue FileNamen eingegangen sind die geladen werden sollen?
Das ist so schlecht nicht.
Ich hätte das nur anders gelöst, aber ich habe ja auch schon fertige Klassen zur Hand. Wenn du dem Loader Thread eine Queue spendierst, in die von aussen die Dateinamen hineingelegt werden, das ganze mit einem event verbindest, dann kannst du den Loader auf das Event warten lassen. Wenn einer einen Namen in die Queue packt, wird das event ausgelöst, und der Loader rennt los. Sollten nach dem Laden noch Namen in der Queue sein, lädt er weiter. Bis alles durch ist.
Der Vorteil ist, das das ganze ziemlich autark arbeitet.
Antworten