Geschwindigkeitsprobleme beim Parsen von binären Dateien

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
cryzed
User
Beiträge: 82
Registriert: Samstag 28. März 2009, 15:53

Hallo,
neulich wurde das Spiel Terraria veröffentlicht. Terraria generiert beim Starten einer neuen spielbaren Welt 20- bis insgesamt 90 Megabyte große *.wld Dateien. Ich parse diese *.wld Dateien momentan mit der Construct Bibliothek für Python und, aufgrund einer Vermutung von mir (Construct sei an irgendeiner Stelle sehr langsam), auch mit meiner eigenen Implementation welche mir das Parsen von binären Dateien ähnlich wie mit der Construct Bibliothek erlaubt.

Mein momentanes Script, welches einem Spieler erlaubt aus den *.wld Dateien Karten der Welt im *.png Format zu erstellen funktioniert zwar, ist aber sehr langsam und speicherlastig, ähnlich wie die alternativen Anfänge mit meiner oben erwähnten Implementation, welche allerdings noch wesentlich langsamer sind.

Mache ich etwas beim Parsen der binären Dateien komplett falsch oder ist Python zum Arbeiten mit großen binären Dateien einfach nicht so gut geeignet? (Ich habe eine C#-Implementation meines Scriptes gesehen welche die Arbeit in 5 Sekunden anstatt 3 Minuten erledigt.)
BlackJack

@cryzed: Zum Speicherproblem: Warum lädst Du die Daten komplett in den Speicher? Construct sollte auch aus Dateiobjekten parsen können. Also nach dem Header müsste man dann einfach die Anzahl von `Tile`\s in einer Schleife theoretisch direkt aus der Datei parsen können. Ansonsten gibt es da ja noch `OnDemand` um Daten erst beim Zugriff zu parsen.
Benutzeravatar
cryzed
User
Beiträge: 82
Registriert: Samstag 28. März 2009, 15:53

Mache ich in der momentanen Version ja nicht. Ich habe vorher den "MetaRepeater" für das Parsen der Tiles benutzt und damit ca. 5 Millionen aufeinmal geparsed -- das führte allerdings zu Speicherproblemen. Als ich das merkte, habe ich probiert Tile für Tile sequentiell nach dem Header zu parsen, welches _extrem_ langsam war (1000 Tiles/30 Sekunden), deswegen benutze ich jetzt momentan "Steps", d.h. anstatt 5 Millionen Tiles aufeinmal mit dem "MetaRepeater" zu parsen, parse ich nur noch ca. 1 Million welches, wie im Source Code kommentiert, ca. 1GB RAM belegt.

Zu "OnDemand": Das habe ich probiert, allerdings mit wenig Erfolg, If/IfThenElse-Clauses im Struct scheinen dann ein Problem zu bekommen wenn sie auf den Context zugreifen wollen, da die nötigen Werte noch nicht "demanded" worden sind -- manuell klappte dies leider auch nicht. Wenn du dich gut mit Construct auskennst (Ich habe mit einem Maintainer im IRC gesprochen [leider ohne viel Erfolg]) würde ich es sehr schätzen wenn du mir erklären könntest wo genau ich OnDemand am besten einsetze.
BlackJack

@cryzed: Du lädst doch am Anfang der `main()` die Datei komplett in den Speicher!? Und dann kopierst Du grosse Teile davon immer wieder mit ``data[offset:]``, wobei da sicher auch zu viel Daten kopiert werden. Im ersten Schleifendurchlauf ja zum Beispiel *alle*.

Ich dachte da an das parsen der Daten mit `parse_stream()` direkt aus der Datei. Ich dachte das geht auch wiederholt, also erst `World` und dann die `Tiles`. Der `offset` den Du momentan per Hand verwaltest, müsste dann der Dateizeiger sein.

Die zweite nahezu identische Schleife für den "rest" würde ich weglassen. Es gibt ja auch Repeater die nicht die volle Anzahl brauchen, sondern beim Ende der Daten einfach abbrechen.

Ansonsten könntest Du noch `psyco` ausprobieren. Oder es halt in einer anderen Sprache schreiben.
Benutzeravatar
cryzed
User
Beiträge: 82
Registriert: Samstag 28. März 2009, 15:53

Du hattest Recht, das war dumm von mir; Leider ist das Problem mit der Geschwindigkeit immer noch da und Psyco unterstützt Python 2.7 noch nicht, PyPy ist auch nicht so viel schneller und unterstützt zusätzlich nicht "einfach so" PIL... hrm.
BlackJack

@cryzed: Die `construct`-Dokumentation behauptet ja, dass man `construct` selbst nicht mehr schneller hinbekommt — solange das in reinem Python und allgemein verwendbar bleiben soll. Wenn man also bei Python bleiben will (im weitesten Sinne :-) müsste man wohl mindestens die Allgemeinheit der Lösung Opfern. Da die `Tile`\s nicht durch eine halbwegs statische Struktur beschrieben werden, sondern Flags beinhalten, welche Länge und Inhalt der folgenden Bytes festlegen und die Strukturen recht klein sind, fällt da viel Arbeit an und man muss wohl den Teil aus dem Bytecode-Interpreter heraus bekommen. Also Cython oder C zum parsen verwenden.
BlackJack

@cryzed: Was Du auf jeden Fall noch probieren könntest, wäre die `Bit`\s durch `UInt8` zu ersetzen. Ausserhalb von `BitStruct`\s machen `Bit`\s nicht viel Sinn, sind aber langsamer.
Benutzeravatar
cryzed
User
Beiträge: 82
Registriert: Samstag 28. März 2009, 15:53

Vielen Dank für die zusätzlichen Antworten, ich werde beides (Port zu C, o.ä.; Bit->ULInt8) ausprobieren.
Antworten