Montag, 21. September 2009

Wie verarbeite ich riesige XML-Dateien in Cocoa?

Am Wochenende habe ich endlich mal wieder ein bisschen an meinem Routenplaner-Projekt weitergemacht. Grundsätzlich habe ich das ganze jetzt auf Core Data umgestellt, mit einem SQLite-Store hintendran.
Will sagen: So bin ich gerüstet, um richtig viel Daten mit akzeptablem Speicherverbrauch verarbeiten zu können.
Theoretisch.
Das Problem ist leider, dass der XML-Parser, der bei Cocoa für event driven XML parsing zuständig ist (NSXMLParser), blösinnigerweise auf ein NSData-Objekt aufsetzt, das den XML-Stream enthält. Das bedeutet, dass die komplette XML-Datei in den Speicher bzw. den virtuellen Speicher geladen wird. Das ist für ein paar hundert MB XML natürlich kein Problem (und wahrscheinlich sogar sinnvoll), aber alleine die schon mehrere Monate alte Version von germany.osm (Kartendaten für Deutschland), die ich für Tests benutze, ist 5,2 GB groß – von planet.osm ganz zu schweigen.
Es gibt zwar die Möglichkeit, über die Option NSDataReadingMapped dafür zu sorgen, dass die Datei selbst direkt als virtual memory benutzt werden kann. Trotzdem macht NSData den Speicher dann fast voll, zieht bei mir zum Beispiel > 2 GB. Dann sind nur noch ein paar hundert MB Speicher frei. Fängt man jetzt an, die mit Daten aus dem XML zu füllen, geht blitzschnell das große Geswappe los, und die Geschwindigkeit tendiert gegen Null.
Es wird mir also nichts anderes übrigbleiben als direkt auf die libxml2 zuzugreifen, die Mac OS X ja auch dabei hat. Ich kann normales C zwar lesen, habe ich aber noch nie eine Zeile Code damit selbst programmiert, insofern graut mir davor ein bisschen. Allerdings habe ich im Netz schon einige Anleitungen dafür gefunden, die zwar allesamt auch auf NSData-Objekten arbeiten, aber wahrscheinlich relativ leicht anpassbar sind. Ich hoffe, dass ich da relativ flott etwas zusammenzimmern kann.
Aber damit hören die Probleme ja nicht auf. Legt man selbst kein NSData-Objekt mit NSDataReadingMapped an, sondern gibt NSXMLParser nur eine File-URL, fängt er gar nicht erst an, die Datei überhaupt zu lesen. Angeblich sei die Datei in Zeile 1, Spalte 1, unerwartet zuende. Macht man es selbst mit NSDataReadingMapped, klappt es erstmal, aber nach etwa einer Stunde unsäglichem Geswappe scheitert er irgendwo zwischen der zwölfmillionsten und der dreizehnmillionsten Zeile mit der Behauptung, ein Tag sei nicht geschlossen worden. Reproduzierbar an der gleichen Stelle, an der aber kein Fehler ist.
Ich vermute, dass ich hier an das 2GB-Limit eines NSData-Objekt in 32bit-Code gestoßen bin. Mein Target ist allerdings x86_64, insofern hatte ich gedacht, dass ich ohnehin 64bit-Code erzeuge. Evtl. muss das aber an anderer Stelle noch konkret aktiviert werden.
Und noch ein Problem gibt es: Im OpenStreetmap-XML werden zunächst alle Nodes definiert, also Punkte auf der Karte. Dann kommen die Ways, also Straßen und Wege, die jeweils eine Referenz auf die IDs der Nodes aufweisen, aus denen sie bestehen. Um später einigermaßen flotten Code zum Routen bauen zu können, muss ich die Ways und ihre Nodes natürlich miteinander verlinken.
Wenn ich einen Way im XML abarbeite, muss ich also anhand ihrer ID die jeweiligen Nodes finden, um sie dann verlinken zu können. Mache ich das mit den in Core Data vorgesehenen Fetch Requests, wird das unendlich langsam – dann dauern schon 20MB XML mehrere Minuten auf meinem brandneuen iMac.
Natürlich kann ich ein NSDictionary benutzen, in dem ich die Nodes anhand ihrer IDs verzeichne. Das ist hübsch flott, könnte aber bei 5GB oder mehr eine ziemlich große Datenstruktur ergeben – schließlich ist dieses Dictionary ja dann nicht von Core Data verwaltet.
Jetzt werde ich also erstmal den XML-Parser umstellen, dann mal testen, ob ich so komplett durch die Datei komme und wieviel Speicher mein flottes Dictionary damit brauchen würde.
Außerdem steht noch an, das in einen eigenen Thread zu packen, damit die Anwendung nicht während des XML-Lesens ständig den Spinning Beachball zeigt.
Ist also noch einiges zut tun, bevor ich überhaupt daran denken kann, mit Routenbrechnung anzufangen …

Keine Kommentare:

Kommentar veröffentlichen