XML Daten Parallel parsen
-
Hi Leute,
ich habe einen Multicore-Prozessor, und möchte den auch möglichst effizient ausnutzen. Nun möchte ich eine große XML-Datei durchparsen und Daten aus ihr rausziehen, leider wird dabei nur ein Kern benutzt. Meine Frage ist nun, wie kann ich also XML-Daten Parallel mit .Net 3.5 bzw. 4.0 parsen, so dass alle Kerne dabei ausgelastet sind? Es muss doch für dieses Problem doch schon eine Lösung geben, oder etwa nicht!?
vielleicht kann mir aus diesem Forum jemand weiterhelfen. Bei Google bin ich leider nicht fündig geworden.
-
Was willst Du denn parallelisieren?
Benutzt Du DOM oder SAX oder einfach den XmlReader?Simon
-
ich will das parsen von bestimmten Knoten parallelisieren.
Bsp.:
<xml version="1.0"> <daten> <title>Titel 1</title> <description>ein beliebiger Text mit Informationen.</description> </daten> <daten> <title>Titel 2</title> <description>ein weiterer Text mit Informationen.</description> </daten> ... </xml>
d.h. ein Kern bearbeitet den 1. "<daten>"-Knoten, der nächste den 2. "<daten>"-Knoten usw.
Als Parser benutze ich den XmlReader.
-
Der XmlReader ist aber "Forward Only" heißt also der kennt die XML Struktur nicht.
Benutz da lieber XPath. Und dann mach es eben mit Asynchronen Methoden aufrufen.
-
Benutz da lieber XPath
Ich nehme an Du meinst DOM, die Queries sind dann XPath.
Ich kann mir schwer vorstellen, dass es Sinn macht DOM Queries zu parallisieren.
Simon
-
Ja die Queries sind XPath und ich mein DOM, is richtig.
Ob es Sinn macht oder nicht, kann uns ja egal sein, wir haben nur eine mögliche Lösung für den Threadsteller sein Problem gepostet
Wenn er uns sagt warum er das machen will, könnten wir ihm eventuell ne bessere Lösung vorschlagen.
-
die sache ist einfach die, dass die XML-Dateien mehrere GB bis zig GB groß sind, und dass das parsen einer einzelnen XML-Datei schonmal ca. 2 Tage in anspruch nehmen kann. um dies zu beschleunigen, wäre es ja sinnvoll, wenn man alle möglichen resourcen ausschöpfen könnte. Ich habe hier ein Core i7 Prozessor mit 4 Kernen und jeder Kern hat nochmal HyperThreading drin. also theoretisch 8 mögliche Prozesse die gleichzeitig laufen können. es wird aber im moment nur einer genutzt, und die Festplatte wird auch nicht ausgelastet.
Der Grund warum ich das also machen will, liegt also praktisch auf der Hand, ich will das parsen der Daten von 2 Tagen auf einen Arbeitstag (8 Stunden) verkürzen und einen praktischen Lerneffekt gibts dazu noch obendrauf, weil die Multiprozessorprogrammierung immer wichtiger wird.
Ich hoffe es ist jetzt verständlich, warum ich das machen will
Dass der XmlReader dafür wahrscheinlich ungeeignet ist habe ich mir auch schon fast gedacht.
Ich habe mir auch schon LINQ to XML bzw. das Parallel Pendant PLINQ to XML kurz angeschaut, das fällt aber auch raus, weil LINQ die kompletten XML-Daten zuerst in den Speicher laden will, bevor es damit anfängt zu arbeiten.Wenn ihr jetzt sagt, dass XPath-Queries/DOM mir bei meinem Problem weiterhelfen kann, dann werde ich mir das mal anschauen. Nett wäre noch, wenn ihr mir sagen könntet, ob das schon im .NET Framework drin ist und welchen Namensraum und welche Klassen ich dafür brauche?
-
DOM kommt nicht in Frage (wegen der Grösse), denn der Trick bei DOM ist, dass alles im Memory gehalten wird.
Eher würde ich einen DB Server aufsetzen, die Daten migrieren und dort effiziente SQL Queries / Stored Procedures absetzen.
Simon
-
ja genau, die Daten die ich aus den XML-Dateien extrahiere, werden ja von mir in einer Datenbank gespeichert. Jedoch gibt es ungefähr einmal im Monat eine aktualisierte XML-Datei, die ich parsen muss :D, deswegen bringt es nicht wirklich was, wenn ich das ganze erst in eine Datenbank packe.
-
I see..
Ev. hilft Dir die Power Threading Library von Jeffrey Richter. http://www.wintellect.com/
-
Naja, mit DOM wirst du nicht glücklich da die Datei zu groß ist und mit dem XMLReader wirst du nicht glücklich da der sequentiell liest. Beides net ganz glücklich. Da hilft auch kein Multithreading irgend einer Art da du die Daten einfach net in der richtigen Form zur Verfügung hast.
Aber vielleicht kann man da ansetzen. Mit dem klassichen Divide & Conquer kann man ja so einiges zügiger bearbeiten. Wenn du genügend Festplattenspeicher zur Verfügung hast, könnte man hingehen und die große Datei teile in kleinere Dateien. Ist bei XML nicht ganz so trivial, aber bei Linq to XML brauchts z.B. keine kompletten XML Dokumente sondern da würde es reichen wenn du nur immer komplette daten Tags hast. Du könntest jetzt jedenfalls die große Datei von oben durchgehen, schreibst die Datentags in nen neues Dokument bis die Datei ne bestimmte Größe hat (so dass sie per XML to Linq noch in Speicher passt) und dann fängst ne neue Datei an usw. Soviele Dateien wie du halt gleichzeitig bearbeiten kannst. Die einzelnen Dateien kannst du jetzt wenn sie getrennt sind ja ganz normal parallel bearbeiten. Wenn die Bearbeitung abgeschlossen ist geht das ganze halt weiter und die große Datei wird in weitere kleinere geteil usw.
Kam die Idee rüber? Im Detail gibts viele knifflige Stellen, aber vom Prinzip her müssts funktionieren. Die Festplattenzugriffe die nötig sind dürften im Vergleich zur jetzigen Laufzeit auch net ins Gewicht fallen.
-
Erstmal ist es wichtig die Bottlenecks zu bestimmen. Als Kandidaten sind da:
-
IO Zugriff: KOmmend ie Daten von Festplatte, Netzlaufwerk etc? Mit welcher Datenrate können die Daten eingelesen werden?
-
Datenbank: Wie werden die Daten in die DB eingetragen? Vor allem hier kann man mächtig Performance killen. Benutzt Du Bulk-Inserts? Ist die Db auf dem gleichen Rechner auf dem auch deine Software läuft oder geht das übers Netz?
Natürlich ist es eine gute Idee alle Kerne zu beschäftigen, aber wenn das eigendliche Parsen am Ende nicht das Bottleneck ist, dann bringt die Verteilung auf mehrere Threads auch nix.
Da dies eine OPtimierungs-Frage ist wäre der erste und wichtigste Schritt einen Profiler zu verwenden um genau festzustellen wo denn die Performance verbraten wird. Nicht zu vergessen das man gerade im NEt Framework auch eine ganze Reihe von Performance-Sünden begehen kann...
Ich habe aktuell auch die Situation wo ich eine Xml-Datei (ca 20MB) in eine Datenbank importiere. Das Laden der Datei dauert ca 1, 2 Sekunden, der tatsächliche Import dann 15 Minuten. Will sagen, der XmlParser an sich ist nicht das Bottleneck.
-
-
@theta: danke für den Link, ich werde es mir mal ansehen und gucken, ob ich was mit anfangen bzw. ob es sich für XML-Verarbeitung eignet.
@Zwergli: vom Prinzip hab ich verstanden was du meinst, jedoch finde ich diesen Ansatz etwas zu umständlich und Suboptimal.
1. weil man die Dateien aufsplitten muss, was wahrscheinlich nicht mal wirklich schwierig ist.
2. weil durch das aufsplitten ein gewisser Versatz im Bezug auf die Beendigung der Prozesse entsteht, d.h. das eine Datei theoretisch 10 Minuten früher abgearbeitet sein könnte als die andere, womit wieder ein Prozessor ungenutzt liegen würde.@loks:
1. IO-Zugriffe sollten keine Bottlenecks darstellen, da die Dateien auf derselben Festplatte(SATA2) liegen, wie das Programm.
2. Die Daten werden über normale Inserts eingefügt. Über Bulk-Inserts habe ich dabei noch nicht nachgedacht. Die meiste Zeit wird jedoch wahrscheinlich beim parsen und verarbeiten der XML-Daten verbraucht. Die Datenbank liegt auch auf dem selben Rechner. Das ganze müsste jedoch tatsächlich erst mit einem Profiler überprüft und bestätigt werden. Jedoch habe ich bisher noch nie mit einem Profiler gearbeitet und weiß auch nicht, was für Profiler es für .NET gibt.Gedankenspiel:
gibt es nicht sowas wie einen Triggergesteuerten XML-Parser, der nach dem einlesen eines Knotens eine bestimmte Funktion Asynchron aufruft und dann gleich mit dem einlesen des nächsten Knotens weitermacht usw.
Natürlich liest er immer nur dann den nächsten Knoten ein, wenn ausreichend Ressourcen, also Prozessoren/Kerne frei sind bzw. eine selbstdefinierte maximale Anzahl an Threads nicht überschritten sind.
Ich hoffe es ist verständlich, wie ich das meine.
-
loks schrieb:
der erste und wichtigste Schritt einen Profiler zu verwenden um genau festzustellen wo denn die Performance verbraten wird.
an der Datenbank ... die arbeitet mit der Festplatte und die ist nunmal langsam ... das einzige wo man trennen könnte wäre zwischen XML parsen und den DB Inserts ... vermutlich wird auch kein StringBuilder sondern String verwendet
-
mogel schrieb:
loks schrieb:
der erste und wichtigste Schritt einen Profiler zu verwenden um genau festzustellen wo denn die Performance verbraten wird.
an der Datenbank ... die arbeitet mit der Festplatte und die ist nunmal langsam ...
So pauschal eine falsche Aussage. Datenbanken sind extrem performant _wenn_ man sie richtig bedient und konfiguriert. Ist schon ein paar Jahre her das ich mit ner Oracle-DB gearbeitet habe, aber schon da gingen locker 1000 Inserts pro Sekunde im Bulk-Modus.
Come to think of it. Bei dem Projekt braucht der DB-Loader anfangs 10 Minuten weil er jeden Datensatz einzeln verarbeitet wurde. Nach Umstellung auf Bulk dauerte der gleiche Vorgang nur noch knapp 6 Sekunden...
-
ok, ich habe vielleicht noch etwas vergessen zu erwähnen.
Und zwar wird nicht bei jedem Knoten etwas in die Datenbank geschrieben, sondern nur wenn in der description gewisse sachen drinstehen. Alles in allem werden bei einer XML-Datei ca. 150000 Einträge in der Datenbank generiert, d.h. über 2 Tage gerechnet entspricht das ca. 1 Eintrag pro Sekunde. Somit sollte die Datenbank kein wirkliches Bottleneck sein.
Selbst wenn es parallisiert wäre und theoretisch bei 8 Kernen nur noch 6 Stunden dauert, sind es 8 Einträge pro Sekunden, was immer noch nicht wirklich viel ist.
-
Könntest du nicht mit regulären Ausdrücken die Knoten die du willst aus der dicken XMl Datei extrahieren,bevor du das Xml verarbeitests?
-
Hallo,
. weil durch das aufsplitten ein gewisser Versatz im Bezug auf die Beendigung der Prozesse entsteht, d.h. das eine Datei theoretisch 10 Minuten früher abgearbeitet sein könnte als die andere, womit wieder ein Prozessor ungenutzt liegen würde.
Nee, wieso? Wenn du 8 Kerne hast, könnten doch 7 das parsen übernehmen und einer ist dediziert für die Bereitstellung der Daten. Und der eine könnte doch immer fortwährend Teile aus der großen Datei zum parsen generieren so dass bei Beendigung eines Parserthreads einfach das neue Dateistück mit angegeben wird und der Thread kann von vorne losgehen. Da muss nichts warten wenn man das geschickt anstellt.
Das mit dem Bulkinsert solltest du dir unbedingt anschaun, das kann massig bringen.
Aber ich find das Parsen an sich dauert auch viel viel zu lange. Ne Sekunde pro Eintrag ist sowas von arschlangsam, selbst wenn die zu parsenden Nodes recht komplex sind. Man kann beim Parsen auch besonders viel falsch machen was z.B. Stringoperationen angeht, unnötige Objekte erstellen usw. Da solltest du mal schaun.
Wärte es möglich vielleicht mal bissle Code von deinem Parsen, am besten mit nem Stück konkreten XML welches geparste werden soll zu sehen? Also zumindest von der Struktur her, richtige Daten müssen ja nicht drin stehen. Mit dem beiden zusammen, Code + XML, kann man ganz gut sagen ob es da noch irgendwie hakt.
-
Andorxor schrieb:
Könntest du nicht mit regulären Ausdrücken die Knoten die du willst aus der dicken XMl Datei extrahieren,bevor du das Xml verarbeitests?
wäre evtl. auch ne Möglichkeit, wenn er das durchsuchen mit regulären Ausdrücken nicht alles auf einmal macht und somit alle treffer gleich in den Speicher laden will!? Wenn das nicht der Fall ist, kann man gucken, ob man das denn parallelisieren kann.
@Zwergli:
Zwergli schrieb:
. weil durch das aufsplitten ein gewisser Versatz im Bezug auf die Beendigung der Prozesse entsteht, d.h. das eine Datei theoretisch 10 Minuten früher abgearbeitet sein könnte als die andere, womit wieder ein Prozessor ungenutzt liegen würde.
Nee, wieso? Wenn du 8 Kerne hast, könnten doch 7 das parsen übernehmen und einer ist dediziert für die Bereitstellung der Daten. Und der eine könnte doch immer fortwährend Teile aus der großen Datei zum parsen generieren so dass bei Beendigung eines Parserthreads einfach das neue Dateistück mit angegeben wird und der Thread kann von vorne losgehen. Da muss nichts warten wenn man das geschickt anstellt.
Das wäre doch dann sowas wie mein Gedankenspiel, oder?
Zwergli schrieb:
Aber ich find das Parsen an sich dauert auch viel viel zu lange. Ne Sekunde pro Eintrag ist sowas von arschlangsam...
nee, da hast du was falsch verstanden. Er schafft so ca. 20-100 XML-<daten>-Knoten pro Sekunde zu parsen, je nach Komplexität, aber von den 20-100 Knoten wird nur ca. einer in die Datenbank eingetragen
Code + XML Schnipsel liefere ich noch nach.
-
entschuldigt, dass ich solange gebraucht habe, aber hier sind der Codeausschnitt und der XML-Part:
XmlReader tWikiXmlReader; FileStream tWikiXmlStream = File.OpenRead(tFileName); string tTitle = ""; string tDescription = ""; tWikiXmlReader = XmlReader.Create(tWikiXmlStream); while (tWikiXmlReader.Read()) { RepeatSql = true; if (tWikiXmlReader.NodeType == XmlNodeType.Element) { if (tWikiXmlReader.Name == "title") { tDescription = ""; tWikiXmlReader.Read(); tTitle = tWikiXmlReader.Value; continue; } else if (tWikiXmlReader.Name == "text") { tWikiXmlReader.Read(); tDescription = tWikiXmlReader.Value; if (tDescription.Length > 0) AllEntries++; } else continue; } if (tDescription.Length > 0 && tWikiXmlReader.NodeType == XmlNodeType.Text) { // hier werden diverse Sachen gefiltert und extrahiert, damit sie in die Datenbankfelder eingetragen werden können. ... // Wenn entsprechende Daten gefunden wurden, dann in die Datenbank eintragen. if(!(Latitude == 0 && Longitude == 0)) { tDbCommand = new MySqlCommand("INSERT INTO enwiki (title, description, lat, long) VALUES (?title, ?description, ?lat, ?long, ?population)", tDbConnection); tDbCommand.Parameters.AddWithValue("?title", Encoding.UTF8.GetBytes(tTitle)); tDbCommand.Parameters.AddWithValue("?description", Encoding.UTF8.GetBytes(tDescription)); tDbCommand.Parameters.AddWithValue("?lat", Encoding.UTF8.GetBytes(Latitude.ToString())); tDbCommand.Parameters.AddWithValue("?long", Encoding.UTF8.GetBytes(Longitude.ToString())); tDbCommand.Parameters.AddWithValue("?population", Encoding.UTF8.GetBytes(Population.ToString())); } } }
und ein XML-Beispiel:
<mediawiki> <page> <title>Actinium</title> <id>3</id> <revision> <text>Chemiches Element ...</text> </revision> </page> ... </mediawiki>