Which is the best for performance wise: XPathNavigator with XPath vs Linq to Xml with query?

Syed picture Syed · Dec 16, 2011 · Viewed 7.1k times · Source

I have an application in which I am using XPathNavigator to iterate nodes. It is working fine.

But I want to know that if I use LINQ to Xml....

  1. What benefits(Performance, maintainability) I will get?

  2. With XPath, LINQ to Xml what is the performance hit?

I am using C#.net, VS 2010 and my .xml is mid size.

Answer

Klaus Nji picture Klaus Nji · May 26, 2013

Just to add onto what has already been stated here, overall performance seems to depends on what you are actually doing with the document in question. This is what I have concluded based on a simple experimental run comparing parsing performance between XElement to XPathNavigator.

If you are selecting nodes, traversing these nodes and reading some attribute values:

  • XElement.Element is faster than XElement.CreateNavigator.Select by an approximate factor of 1.5.
  • XElement.CreateNavigator.Select is faster than XPathNavigator.Select by an approximate factor of 0.5.
  • XPathNavigator.Select is faster than XElement.XPathSelectElement by an approximate factor of 0.5.

On the other hand, if you are also reading the value of each node's children it gets a little interesting:

  • XElement.Element is faster than XElement.XPathSelectElements by an approximate factor of 0.5.
  • XElement.XPathSelectElement is faster than XPathNavigator.Select by an approximate factor of 3.
  • XPathNavigator.Select is faster than XElement.CreateNavigator.Select by an approximate factor of 0.5.

These conclusions are based on the following code:

 [Test]
    public void CompareXPathNavigatorToXPathSelectElement()
    {     
        var max = 100000;

        Stopwatch watch = new Stopwatch();
        watch.Start();

        bool parseChildNodeValues = true;

        ParseUsingXPathNavigatorSelect(max, watch, parseChildNodeValues);
        ParseUsingXElementElements(watch, max, parseChildNodeValues);
        ParseUsingXElementXPathSelect(watch, max, parseChildNodeValues);
        ParseUsingXPathNavigatorFromXElement(watch, max, parseChildNodeValues);
    }

    private static void ParseUsingXPathNavigatorSelect(int max, Stopwatch watch, bool parseChildNodeValues)
    {
        var document = new XPathDocument(@"data\books.xml");
        var navigator = document.CreateNavigator();

        for (var i = 0; i < max; i++)
        {
            var books = navigator.Select("/catalog/book");
            while (books.MoveNext())
            {
                var location = books.Current;
                var book = new Book();
                book.Id = location.GetAttribute("id", "");

                if (!parseChildNodeValues) continue;

                book.Title = location.SelectSingleNode("title").Value;
                book.Genre = location.SelectSingleNode("genre").Value;
                book.Price = location.SelectSingleNode("price").Value;
                book.PublishDate = location.SelectSingleNode("publish_date").Value;
                book.Author = location.SelectSingleNode("author").Value;
            }
        }

        watch.Stop();
        Console.WriteLine("Time using XPathNavigator.Select = " + watch.ElapsedMilliseconds);
    }

    private static void ParseUsingXElementElements(Stopwatch watch, int max, bool parseChildNodeValues)
    {
        watch.Restart();
        var element = XElement.Load(@"data\books.xml");

        for (var i = 0; i < max; i++)
        {
            var books = element.Elements("book");
            foreach (var xElement in books)
            {
                var book = new Book();
                book.Id = xElement.Attribute("id").Value;

                if (!parseChildNodeValues) continue;

                book.Title = xElement.Element("title").Value;
                book.Genre = xElement.Element("genre").Value;
                book.Price = xElement.Element("price").Value;
                book.PublishDate = xElement.Element("publish_date").Value;
                book.Author = xElement.Element("author").Value;
            }
        }

        watch.Stop();
        Console.WriteLine("Time using XElement.Elements = " + watch.ElapsedMilliseconds);
    }

    private static void ParseUsingXElementXPathSelect(Stopwatch watch, int max, bool parseChildNodeValues)
    {
        XElement element;
        watch.Restart();
        element = XElement.Load(@"data\books.xml");

        for (var i = 0; i < max; i++)
        {
            var books = element.XPathSelectElements("book");
            foreach (var xElement in books)
            {
                var book = new Book();
                book.Id = xElement.Attribute("id").Value;

                if (!parseChildNodeValues) continue;

                book.Title = xElement.Element("title").Value;
                book.Genre = xElement.Element("genre").Value;
                book.Price = xElement.Element("price").Value;
                book.PublishDate = xElement.Element("publish_date").Value;
                book.Author = xElement.Element("author").Value;
            }
        }

        watch.Stop();
        Console.WriteLine("Time using XElement.XpathSelectElement = " + watch.ElapsedMilliseconds);
    }

    private static void ParseUsingXPathNavigatorFromXElement(Stopwatch watch, int max, bool parseChildNodeValues)
    {
        XElement element;
        watch.Restart();
        element = XElement.Load(@"data\books.xml");

        for (var i = 0; i < max; i++)
        {
            // now we can use an XPath expression
            var books = element.CreateNavigator().Select("book");

            while (books.MoveNext())
            {
                var location = books.Current;
                var book = new Book();
                book.Id = location.GetAttribute("id", "");

                if (!parseChildNodeValues) continue;

                book.Title = location.SelectSingleNode("title").Value;
                book.Genre = location.SelectSingleNode("genre").Value;
                book.Price = location.SelectSingleNode("price").Value;
                book.PublishDate = location.SelectSingleNode("publish_date").Value;
                book.Author = location.SelectSingleNode("author").Value;
            }
        }

        watch.Stop();
        Console.WriteLine("Time using XElement.Createnavigator.Select = " + watch.ElapsedMilliseconds);
    }

with books.xml downloaded from here

Overall, it looks like the XElement parsing API, excluding the XPath extensions, gives you the best performance, while also easier to use, if your document is somewhat flat. If you have deep nested structures where you have to do something like

XElement.Element("book").Element("author").Element("firstname").SomethingElse()

then XElement.XPathSelectElement may provide the best compromise between performance and code maintainability.