Multithreaded File loader
-
Hey Leuchte ich hätte da mal eine Frage bezüglich eines File Managers, der Dateien parallel mit vielen Threads ließt und in Buffer verteilt.
Und zwar arbeite ich gerade an einer 3D Engine and bin dabei einen Meshloader zu implementieren.Meine ersten Ideen gingen in Richtung Multithreading, weil die Gamelogic und der Renderer auf jedenfall parallel weiter laufen müssen.
Die Idee war durch ein eigenes Mesh format Datenchucnks zu haben für verschiedene Typenvon Daten wie Vertices,Indecies,Bones,Materials.....
Ganz am Anfang hat man einen Parser ,der diese binären Chuncks überfliegt und den Bereich bzw. die Größe des Chuncks ließt und diese dan an die Threads übergibt.
Dann fangen die Threads an ihren Bereich zu laden und die Direct3D Buffer zu laden.Das tolle ist, dass ich keine Angst haben muss, dass sich diese gegenseitig behindern, da man für die Typen (vertices,indices.....) eigene Buffer hat.Jetzt hab ich ein paar performance Tests gemacht mit den standart fstream Methoden usw. un habe bei einer Größe von 67.3 MB eine ladezeit von 170 ms ,was denk ich mal ganz gut ist.
Ich habe folgenden Code ausprobiert, um die Datei 2 mal zu lesen und in den gleichen Buffer zu laden, bekomme aber nur Schrott( vorerst erste Übung vor den threads)
Dabei lese ich 2 mal und setzte erst auf den anfang der Datei und lese die hälfte und dann nochmal die Hälfte von der bleibenden position, bekomme jedoch irgendwas falsches raus.Würde mich über Hilfe freuen

Etwas Code:
[cpp]ifstream file("model.bin",ios::in | ios::binary | ios::ate);size = file.tellg();
char* block = new char[size];file.seekg(0, ios::beg);
file.read(block, size / 2 );file.seekg(0, ios::cur);
file.read(block, size / 2);file.close();
-
Bob Wolfskin schrieb:
Dabei lese ich 2 mal und setzte erst auf den anfang der Datei und lese die hälfte und dann nochmal die Hälfte von der bleibenden position, bekomme jedoch irgendwas falsches raus.
Aha
Bob Wolfskin schrieb:
Würde mich über Hilfe freuen

Würde mich über eine Frage freuen.
EDIT: Ich will mal nicht so sein.
ifstream file("model.bin",ios::in | ios::binary | ios::ate); size = file.tellg(); char* block = new char[size];Meintest du hier vielleicht
vector<char>?file.seekg(0, ios::beg); file.read(block, size / 2 );Erste Hälfte vom Puffer wird beschrieben mit erster Hälfte der Datei.
file.seekg(0, ios::cur);Tut gar nichts. Ist dir das klar?
file.read(block, size / 2);Erste Hälfte vom Puffer wird überschrieben mit zweiter Hälfte der Datei.
Bei einer ungeraden Dateigröße wird jedoch das letzte Byte nie gelesen.file.close();Unnötig, lass das weg.
-
Ja sorry, dass ich so eine Scheiße Schreibe.
Es geht mir einfach darum erst den ersten Teil der Datei in den puffer zu laden und dann die zweite hälfte (dachte man setzt position mit file.seekg(0, ios::cur);Es soll aber vom char auch die position bleiben, die man nach dem ersten schreiben hat, sonst ist das ganze sinnlos.
-
Weiß da keiner was ??
-
Tyroxx hat es doch schon beantwortet:
ifstream file("model.bin",ios::in | ios::binary | ios::ate); size = file.tellg(); char* block = new char[size]; file.seekg(0, ios::beg); file.read(block, size / 2 ); file.seekg(0, ios::cur); file.read(block, size / 2); file.close();beim letzten read musst Du etwas in der Art von
file.read(block+size/2, size / 2);
machen. Ansonsten überschreibst Du Deinen ersten Block.Davon, dass Deine Datenstruktur - nun ja - nicht ideal ist, möchte ich erst mal nicht reden.
Deine Zeitmessung ist beeindruckend. Liest Du von einer SSD oder RAM-Disk?
-
Es muss natürlich heissen:
file.read(block+size/2, size - size/2);Sonst wird bei einer ungerade Zahl von Zeichen das letzte nicht gelesen (hatte Tyroxx aber auch schon geschrieben)
-
Vielen Dank für die Antworten.
Ja ich lese von einer SSD (550 MB/s lese,500 MB/s schreibe ;))Zur Datenstruktur kann ich nur sagen, dass das jetzt nur ein Test ist, wird natürlich ganz anders aussehen mit Chuncks usw.
Das Problem ist, dass wenn ich 4 6 MB Dateien lese (model,2 texturen und eine material datei) dauert das ganze ca. 400 ms, ich meine wenn ich dann ein ganzes Level lade dauert das sicher (sagen wir mal ein größeres Levels mit allen modelen etc.. ca. 400 MB) ca. 10 Sekunden, deshalb will ich es mit threads versuchen, sodass beim laden des levels auch alle Modelle parallel geladen werden
-
Danke hat funktioniert

Gibt es denn eine Möglichkeit die position des file pointers selbst zu bestimmen ???
Ich möchte ja mit threads arbeiten und die sollen ja parallel verschiedene Stellen der Datei laden und in puffer packen, ich kenn jetzt nur diese ios::beg,cur und end variablen und frage mich jetzt ,wie man das ganze selbst bestimmen kann.Sagen wir ich mache genau das gleiche wie davor (den code denn ich gepostet habe) nur mit 2 Threads.Da diese die Datei in 2 Teile teilen, muss jetzt der eine von ios::beg anfangen und einen lenght/2 großen block lesen und der andere fängt bei der position lenght/2 an und geht bis ios::end.
Wie wäre das möglich ?
-
seekg(length / 2);? Eine Überladung hat doch streampos als Parametertypen.Ich frage mich nur gerade, ob das Sinn macht. Wo liegt denn bei so einem Lesevorgang der Bottleneck? Wird der mit MT behoben? Irgendwie bezweifle ich das...
-
Also ich seh mir das ganze gerade an und muss sagen es ist sinnlos.Wenn ich einen Datei habe ,wo ich einen streampointer habe und die Datei lese macht es kein sinn in 2 threads parallel von verschiedenen Stellen der Datei zu lesen ,weil die Threads ja nicht parallel laufen sondern nacheinander und da muss der positionspointer aufder Festplatte immer hin und her springen, was kein Sinn macht.
Ich habe mir überlegt einfach bei jedem Mesh, dass man laden will ein Thread zum Lesen von diesem zu erstellen, damit die anderen Komponenten flüssig weiter laufen können.
Jetzt hab ich meinen FilManager als Singleton deklariert, was denk ich mal das sinnvollste ist, da ich davon eben nur eine Instanz brauche und diesen quasie wie eine Funktion verwende.
Jetzt will ich ja beim Laden eines Meshes in dieser Klasse ein thread erstellen (also bei jedem Laden des Meshes wird eins erstellt ) und ich muss die HANDLE zu jedem Thread in einer STL list speichern (oder Vector).Dann müsste ich aber beim Update der liste/vector gucken, ob die threads alle laufen und wenn nicht diese Stelle der liste/vector entfernen. Gibts da eine elegantere Methode ??
Danke
-
Herr Bob Wolfskin,
was das reine Lesen angeht (Bytes rumschaufeln) gibt es nix schnelleres als lineares Lesen.
Dein Problem liegt auch wo anders, und zwar vermutlich beim Parsen. Und das meiste wirst du dabei nicht mit multithreading rausholen, sondern mit nem besseren (schnelleren) Parser.Bzw. mit einem "schnelleren" binären File-Format.
Und was Threads angeht ... pfuh. Ich hab' nicht den Eindruck dass du das nötige Hintergrundwissen hast um korrekt mit Threads zu arbeiten.
-
Naja ich habe eigendlich bisher keine Probleme mit dem Parser , da ich ein bekanntes Formatdesign verwende.In der Tat habe ich nicht so viel mit Threads zu tun gehabt , jedoch muss ich nur für jedes Mesh was geladen wird eine Threadfunkton erstellen und da drinnen ganz normal parsen/ laden... das wars mit den Threads.Ich erstellte dafür eine map mit den HANDLE von jedem thread und ein DWORD für den thread zustand ( warte bis der thread fertig ist).Dann wird in der Update Funktion durch einen iterator dieser dword wert abgefragt und falls derthread fertig ist dieser aus der map mit dem handle gelöscht.
-
Also... äh.
Wenn du davon ausgehst dass das Parsen nicht der Flaschenhals ist, dann brauchst du keine Threads. Denn Threads können keine Daten von der Festplatte/SSD laden. Das macht der SATA Controller auf deinem Mainboard, und während der das macht macht der Thread heia.
Nur wird wie gesagt dort nicht der Flaschenhals sein, sondern bei der Verarbeitung der Daten - dem was ich mit "Parsen" bezeichne.
Und da kann man oft viel mehr mit anderem Code rausholen als mit mehreren Threads möglich wäre. Also vorausgesetzt du hast jetzt nicht 10 oder 100 Cores in deinem Rechner.
Natürlich kann man dann zusätzlich noch mehrere Threads parallel laufen lassen, um das ganze noch weiter zu beschleunigen. Das würde ich aber erst als zweites angehen, weil der zu erwartende Gewinn dabei eben nicht so toll ist. Mehr als Faktor 4 wirst du da mit aktuellen PCs kaum rausbekommen.
BTW: was bitte ist ein "Formatdesign"?
-
Ich glaube du verstehst das etwas falsch mit den Threads.
Ich arbeite schon viele Jahre mit einer 3D Engine ,wo das Laden von Dateien und Ressourcen leider nicht abstract in einem separaten Thread läuft, sondern ganz normal im Hauptthread der Anwendung, was zur Folge hat, dass beim Laden von Modelen/Texturen etc. alles angehalten wird (Renderer,physics Simulation.....)
Es ist z.B nicht möglich wie bei fast allen Spielen währen der am Anfang laufenden Splashscreens Daten für z.B das Menu und für das Spiel selbst zu laden, weil einfach nichts mehr aktualisiert wird.
Als "Format design" meine ich das Design eines Datei formates, was in vielen DirectX Büchern verwendet wird.Eigendlich ein einfaches, jedoch schnelles Konzept ,wo man mit Chuncks arbeitet.
Die Threads sind dazu gedacht, da der Filemanager nur eine Instanz hat ,wird bei jedem Aufruf zum Laden von irgendwas (Mesh,Texture....) ein neuer Thread erstellt und dort drin wird das Mesh/texture etc. geladen, ohne dass die anderen Komponenten dadurch angehalten werden müssen.Es geht mir nicht darum irgendwie alle Modele gleichzeitig laden zu können, eher darum das ganze System nicht anzuhalten
-
welche engine ist das denn? so ein hobby-ding? "filemanager" hört sich stark nach heimarbeit an.
wenn die intern nicht synchronisiert wird, kannst du nicht einfach so ressourcen aus einem anderen thread reinschaufeln.
-
Ja das ist eine Heimarbeit.
Ich habe jetzt eine Threadfunktion mit einer struktur als Parameter (MeshLoaderParam enthält GMesh* mesh und std::string path )
Der pointer zu mesh wird dann in dem Thread ausgefüllt (bzw. die Buffer)Denkst du dass funktioniert nicht ?
-
das kann man so pauschal nicht beantworten. wenn multithreading in der engine nicht vorgesehen ist, wirst du schlechte karten haben. dann darfst du im prinzip keine funktionen aus anderen als dem hauptthread aufrufen. das gibt dann bestensfalls abstürze oder (wahrscheinlicher) "unerklärliches" verhalten, das kaum reproduzierbar ist.
wenn du die daten selbst im hintergrund in den speicher laden und dann später im hauptthread der engine zum parsen geben kannst, hast du bessere chancen.
-
@Bob Wolfskin
Naja, die Frage ist ob die Mesh-Ladefunktion thread-safe ist.
Und das können wir die nicht beantworten, wenn wir nicht wissen womit du arbeitest.
Wenn es eine selbst gebastelte Engine ist, und du dich mit Threads noch nicht so wirklich auskennst, dann stehen die Chancen relativ schlecht.