Autor Beitrag
danielf
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 1012
Erhaltene Danke: 24

Windows XP
C#, Visual Studio
BeitragVerfasst: Mi 28.02.18 13:54 
Hallo,

ich habe ("größere" ~200MB) Xml Dateien und möchte diese so schnell wie möglich einlesen und verarbeiten. Hierfür verwende ich den XmlReader. Ich lese zuerst die Datei als MemoryStream ein und übergebe sie dann dem XmlReader und lese die gewünsche Informationen aus. Die Datei einzulesen geht schnell (200ms), aber das Parsen der Struktur benötigt um die 5 Sekunden. Dies würde ich gerne beschleunigen.

Mein Idee war, dass ich einzelne Top-Level-Knoten parallel auslese.

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
private void Parse(string file)
{
   var ms = new MemoryStream(File.ReadAllBytes(file));

   using (XmlReader xr = XmlReader.Create(ms))
   {
      xr.ReadStartElement();

      ReadTopLevelNodes(xr);
   }  
}

private void ReadTopLevelNodes(XmlReader xr)
{
   var parallelActions = new List<Action>();
   while (xr.Read())
   {
      if (xr.NodeType == XmlNodeType.Element)
      {
         var subTreeReader = xr.ReadSubtree();
         parallelActions.Add(() => ReadSubTree(subTreeReader));
         xr.Skip();
      }
   }

   Parallel.Invoke(parallelActions.ToArray());
}


Das funktioniert leider nicht, weil bei ReadSubTree das xr nicht weiter gelesen werden darf (hier wegen dem async und xr.Skip()). Deshalb habe ich versucht den mit ReadOuterXml und StringReader einen neuen XmlReader zu nehmen um den SubTree zu parsen. Dies war von der Performance aber mieser.

Hat von euch einer eine Idee wie ich Xml-Äste parallel auslesen kann?

Danke und Gruß
Daniel
jfheins
ontopic starontopic starontopic starontopic starontopic starontopic starofftopic starofftopic star
Beiträge: 853
Erhaltene Danke: 139

Win7
VS 2013, VS2015
BeitragVerfasst: Di 06.03.18 21:38 
Ich hatte auch mal mit etwas größeren XML-Dateien zu tun.

Das Problem (bzw. meine Erklärung dafür) ist folgendes: Um den nächsten Subtree auszulesen, musst du zunächst den vorigen zu Ende lesen, da du nur dann weißt, dass er euch zu Ende ist.
Du kannst in einem XML nicht einfach zum passenden End-Tag springen ohne den Teil dazwischen zu lesen (und darin Start- und End-Tags zu verfolgen).

Was mir geholfen hat, war wirklich nur die benötigten Teile als XElement zu parsen und am Rest möglichst vorbei zu lesen. Zum Beispiel:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
      while (reader.Read() && reader.NodeType == XmlNodeType.Element && reader.LocalName == "ABC"// Wichtig: filtern vor XElement.Load()
      {
        using (var subtree = reader.ReadSubtree())
        {
          var xmlNode = XElement.Load(subtree);
          // Das folgende ggf. als Task auslagern:
          var abc = ABC.FromXElement(xmlNode , _internalState);
          this.ABCs.Add(abc);
        }
      }

Wobei du in diesem Beispiel natürlich den xmlNode als Task (parallel) verarbeiten kannst. Ich habe mich damals nicht sehr um multithreading beim parsen einer Datei bemüht, da ich typischerweise mehrere Dateien parsen musste und da parallelisieren konnte.

Hast du eigentlich mal den Profiler genutzt um zu sehen, wo genau die Zeit bleibt? Ich meine, bei mir gingen damals 20MB in einer halben Sekunde...