Some hard numbers about XmlDocument, XDocument and XmlReader (x86 versus x64)
In my earlier blog I had summarized from experience and material on the web that was born out by my own observations. I decided to construct a very simple demo program as shown at the bottom of this blog. I loaded the same file with each approach and counted the nodes in a Xml file. The box was quad-x64 core with 12 Gigs of ram.
The results are very information for guidance.
x86 | Memory (K) | Msec | Memory Delta |
No Op | 3196 | 1.95 | |
XmlDocument | 43912 | 1221.70 | 40716 |
XDocument | 34700 | 836.90 | 31504 |
XmlReader | 3696 | 299.80 | 500 |
x64 | Memory (K) | Msec | Memory Delta |
No Op | 3184 | 0.98 | |
XmlDocument | 74900 | 1076.20 | 71716 |
XDocument | 48252 | 782.23 | 45068 |
XmlReader | 5280 | 301.76 | 2096 |
First, as expected x64 was faster by about 20% – not a huge amount. What was very interesting is that the memory consumption – from 43% more to 319% more; in other words a web application that uses XML heavy that runs in 3Gigs of memory if compiled as x86 could require 12Gigs of memory if compiled as x64 for perhaps a 20% increase of performance. It makes more sense to run two instances of the web site compiled as x86 and double the performance using only 6 Gigs of memory.
As expected, XmlReader was the best performing:
- A 3 Gig Xml-Heavy web application using XmlDocument would run THREE times faster using only 100 megs of memory in X64 --- yes, 1/35 of the memory needs. In x86, it’s more shocking: only 36 megs of memory would be needed!
- For XDocument, the magnitude is not as severe, but still a magnitude different!
If you wish to try the code, here is the Console app code. The memory used is obtained by looking at Task Manager(I did not clean up objects intentionally so task manager would show the peak memory).
using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; using System.Xml; using System.IO; using System.Text; namespace XmlPerformance { class Program { static void Main(string[] args) { int option=0; int cnt = 0; FileInfo xmlFile=null; foreach (var arg in args) { if (arg.Length == 1) { int.TryParse(arg, out option); } else if(xmlFile==null || ! xmlFile.Exists) { xmlFile = new FileInfo(arg); } } if (xmlFile.Exists) { DateTime start = DateTime.Now; switch (option) { case 1: XmlDocument dom = new XmlDocument(); dom.Load(xmlFile.FullName); cnt = dom.SelectNodes("//*").Count; break; case 2: XDocument doc = XDocument.Load(xmlFile.FullName); foreach (XElement e in doc.Descendants()) { cnt++; } break; case 3: XmlReader rdr = XmlReader.Create(xmlFile.FullName); while (!rdr.EOF) { rdr.Read(); if (rdr.NodeType == XmlNodeType.Element) { cnt++; } } break; default: break; } DateTime end = DateTime.Now; TimeSpan elapse=end-start; Console.WriteLine(String.Format("{0} msec for {1} elements. File Size is:{2}",elapse.TotalMilliseconds,cnt,xmlFile.Length)); Console.ReadKey(); } } } }
A simple cmd file was used:
XmlPerformance 0 L:\PortableDrive\sussen\QuickTest\bin\Debug\Results.xml
XmlPerformance 1 L:\PortableDrive\sussen\QuickTest\bin\Debug\Results.xml
XmlPerformance 2 L:\PortableDrive\sussen\QuickTest\bin\Debug\Results.xml
XmlPerformance 3 L:\PortableDrive\sussen\QuickTest\bin\Debug\Results.xml
A good test involves warm up, processor afinity/priority change, RELEASE config and a Run Without Debug start. Here's the right test code:
ReplyDeleteusing System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Xml;
using System.Xml.Linq;
namespace XmlPerformance
{
class Program
{
static FileInfo _xmlFile;
//static Stream _stream;
static void Main(string[] args)
{
Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(2); // Uses the second Core or Processor for the Test
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime; // Prevents "Normal" processes from interrupting Threads
Thread.CurrentThread.Priority = ThreadPriority.Highest; // Prevents "Normal" Threads from interrupting this thread
_xmlFile = new FileInfo(@"C:\file.xml");
//_stream = File.OpenRead(@"C:\file.xml");
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("Warmup");
var stopWatch = new Stopwatch();
stopWatch.Start();
while (stopWatch.ElapsedMilliseconds < 1200) // A Warmup of 1000-1500 mS
// stabilizes the CPU cache and pipeline.
{
WarmUp(TestXmlDocument);
WarmUp(TestXDocument);
WarmUp(TestXmlReader);
}
stopWatch.Stop();
for (int i = 0; i < 5; i++)
{
Console.WriteLine();
Console.WriteLine("Test set " + i);
Console.WriteLine();
RunTest("Testing XmlDocument...", TestXmlDocument);
RunTest("Testing XDocument...", TestXDocument);
RunTest("Testing XmlReader...", TestXmlReader);
Console.WriteLine();
}
Console.ReadKey();
}
private static void TestXmlDocument()
{
var dom = new XmlDocument();
dom.Load(_xmlFile.FullName);
int i = dom.SelectNodes("//*").Count;
}
private static void TestXDocument()
{
int i = 0;
XDocument doc = XDocument.Load(_xmlFile.FullName);
foreach (XElement e in doc.Descendants())
i++;
}
private static void TestXmlReader()
{
int i = 0;
XmlReader rdr = XmlReader.Create(_xmlFile.FullName);
while (!rdr.EOF)
{
rdr.Read();
if (rdr.NodeType == XmlNodeType.Element)
{
i++;
}
}
}
private static void RunTest(string header, TestMethod testMethod)
{
var stopWatch = new Stopwatch();
if (header != null)
Console.WriteLine(header);
stopWatch.Start();
testMethod();
stopWatch.Stop();
if (header != null)
{
Console.WriteLine("Executed in {0} ticks", stopWatch.ElapsedTicks);
Console.WriteLine();
}
}
private static void WarmUp(TestMethod testMethod)
{
RunTest(header: null, testMethod: testMethod);
}
delegate void TestMethod();
}
}
With the code i posted, the performance difference is smaller
ReplyDeleteYou know about the differences between SAX and DOM?
ReplyDelete