56

NOTE: If you experience this issue as well, please upvote it on Apache JIRA:

https://issues.apache.org/jira/browse/XALANJ-2540

I have come to an astonishing conclusion that this:

Element e = (Element) document.getElementsByTagName("SomeElementName").item(0);
String result = ((Element) e).getTextContent();

Seems to be an incredible 100x faster than this:

// Accounts for 30%, can be cached
XPathFactory factory = XPathFactory.newInstance();

// Negligible
XPath xpath = factory.newXPath();

// Negligible
XPathExpression expression = xpath.compile("//SomeElementName");

// Accounts for 70%
String result = (String) expression.evaluate(document, XPathConstants.STRING);

I'm using the JVM's default implementation of JAXP:

org.apache.xpath.jaxp.XPathFactoryImpl
org.apache.xpath.jaxp.XPathImpl

I'm really confused, because it's easy to see how JAXP could optimise the above XPath query to actually execute a simple getElementsByTagName() instead. But it doesn't seem to do that. This problem is limited to around 5-6 frequently used XPath calls, that are abstracted and hidden by an API. Those queries involve simple paths (e.g. /a/b/c, no variables, conditions) against an always available DOM Document only. So, if an optimisation can be done, it will be quite easy to achieve.

My question: Is XPath's slowness an accepted fact, or am I overlooking something? Is there a better (faster) implementation? Or should I just avoid XPath altogether, for simple queries?

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
  • 4
    Have you tried comparing your results with that of a [compiled](http://download.oracle.com/javase/6/docs/api/javax/xml/xpath/XPath.html#compile%28java.lang.String%29), reusable [XPathExpression](http://download.oracle.com/javase/6/docs/api/javax/xml/xpath/XPathExpression.html)? – McDowell Jun 14 '11 at 08:18
  • Is it problematic slow? One possibility is to evaluate another library, such as `jaxen`. – Johan Sjöberg Jun 14 '11 at 08:19
  • @Johan: It is. Typically, those queries tend to take 2-5ms a piece, or 10% of a user's request time. We think this starts being problematic, as we're going to use more XPath in the future. `jaxen` might indeed be an option in the future... – Lukas Eder Jun 14 '11 at 08:25
  • @McDowell: Caching the expression is negligible, especially compared to caching of the factory. But after some re-tests, I have to correct the times. Caching the factory will accelerate roughly by 30% – Lukas Eder Jun 14 '11 at 08:28
  • For those interested, I have added an answer to the question – Lukas Eder Jun 14 '11 at 09:45
  • @Johan, I also measured `jaxen`. It's even worse in my test case, compared to `saxon` or `xalan`... – Lukas Eder Jun 14 '11 at 13:32
  • @Lukas, Interesting. Good job performing the test. – Johan Sjöberg Jun 14 '11 at 13:41
  • @Johan, yeah, nice to know that after all our technology choice was the right one, even if `xalan` looks like a dinosaur :-) – Lukas Eder Jun 14 '11 at 13:46
  • @johan,The best xpath performance is vtd-xml, have you tried that? – vtd-xml-author Jul 29 '13 at 08:12
  • see also: https://stackoverflow.com/q/3782618/363573 – Stephan Jul 03 '18 at 13:04

3 Answers3

63

I have debugged and profiled my test-case and Xalan/JAXP in general. I managed to identify the big major problem in

org.apache.xml.dtm.ObjectFactory.lookUpFactoryClassName()

It can be seen that every one of the 10k test XPath evaluations led to the classloader trying to lookup the DTMManager instance in some sort of default configuration. This configuration is not loaded into memory but accessed every time. Furthermore, this access seems to be protected by a lock on the ObjectFactory.class itself. When the access fails (by default), then the configuration is loaded from the xalan.jar file's

META-INF/service/org.apache.xml.dtm.DTMManager

configuration file. Every time!:

JProfiler profiling results

Fortunately, this behaviour can be overridden by specifying a JVM parameter like this:

-Dorg.apache.xml.dtm.DTMManager=
  org.apache.xml.dtm.ref.DTMManagerDefault

or

-Dcom.sun.org.apache.xml.internal.dtm.DTMManager=
  com.sun.org.apache.xml.internal.dtm.ref.DTMManagerDefault

The above works, as this will allow to bypass the expensive work in lookUpFactoryClassName() if the factory class name is the default anyway:

// Code from com.sun.org.apache.xml.internal.dtm.ObjectFactory
static String lookUpFactoryClassName(String factoryId,
                                     String propertiesFilename,
                                     String fallbackClassName) {
  SecuritySupport ss = SecuritySupport.getInstance();

  try {
    String systemProp = ss.getSystemProperty(factoryId);
    if (systemProp != null) { 

      // Return early from the method
      return systemProp;
    }
  } catch (SecurityException se) {
  }

  // [...] "Heavy" operations later

So here's a performance improvement overview for 10k consecutive XPath evaluations of //SomeNodeName against a 90k XML file (measured with System.nanoTime():

measured library        : Xalan 2.7.0 | Xalan 2.7.1 | Saxon-HE 9.3 | jaxen 1.1.3
--------------------------------------------------------------------------------
without optimisation    :     10400ms |      4717ms |              |     25500ms
reusing XPathFactory    :      5995ms |      2829ms |              |
reusing XPath           :      5900ms |      2890ms |              |
reusing XPathExpression :      5800ms |      2915ms |      16000ms |     25000ms
adding the JVM param    :      1163ms |       761ms |        n/a   |

note that the benchmark was a very primitive one. it may well be that your own benchmark will show that saxon outperforms xalan

I have filed this as a bug to the Xalan guys at Apache:

https://issues.apache.org/jira/browse/XALANJ-2540

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
  • Very interesting. I would be fascinated to see what numbers you get if you substitute the Saxon implementation of XPathFactory. I know there is a big overhead for searching the classpath for the XPath factory, and I suspect there's a significant overhead for parsing the XPath expression, but I think the actual execution should be pretty fast. – Michael Kay Jun 14 '11 at 10:10
  • @Michael: You're affiliated with Saxon, I see? I downloaded the home edition and ran my benchmarks against it. Where Xalan had 5800ms, Saxon took 16000ms... – Lukas Eder Jun 14 '11 at 10:34
  • A great, low cost optimisation - Thanks Lukas! I note that re-using XpathFactory, Xpath, XpathExpression is not possible across multiple threads, all three are unfortunately not thread-safe. Did you find any further optimisation of quicker alternative libraries?? – joelittlejohn Jun 17 '11 at 16:31
  • @joelittlejohn, nope. Only what I've mentioned. 1) transform simple `XPath` expressions into DOM API calls, 2) add the JVM parameter and maybe I'm going to add 3) a shared pool of `XPathFactory` to thread-safely reuse them. With these measurements, I expect `XPath` queries to take only about 5% of today's `XPath` CPU time. – Lukas Eder Jun 17 '11 at 18:31
  • I'm surprised Saxon was so slow relative to Xalan -- usually it is quite a bit faster. Do you have the code for the benchmark? – Raman Oct 26 '12 at 00:50
  • @Raman: No, unfortunately, I don't have it anymore... It was somewhat equivalent to the code in the question, though... – Lukas Eder Oct 26 '12 at 06:53
  • 1
    See also the 2009 blog post http://scn.sap.com/community/java/blog/2009/12/04/performance-improvements-in-nw-java-applications-with-xml-processing – JasonPlutext Nov 13 '13 at 22:37
  • @JasonPlutext: Thanks for linking! Interesting to see that this had been discovered two years earlier. Too bad the author of that article didn't already file a bug. I'll cross link to this question. – Lukas Eder Nov 14 '13 at 08:52
  • @Lukas In my benchmarks, Saxon is faster than xalan: https://github.com/gatling/xpath-benchmark – Stephane Landelle May 03 '14 at 20:11
  • @StephaneLandelle: It was a very quick benchmark testing only my very specific XPath expression, which I made on the request of Michael Kay (first comment) three years ago. It may well be that Saxon has improved, in the mean time. – Lukas Eder May 04 '14 at 05:59
  • Have not seen anyone reference the -D parameter used to optimize the XPath factory, detailed here: https://stackoverflow.com/questions/40955003/xpathfactoryimpl-not-found-error-using-mybatis/44246978#44246978 – Erik Ostermueller Nov 15 '22 at 13:13
6

Not a solution, but a pointer to the main problem: The slowest part of the process for evaluating an xpath in relation to an arbitrary node is the time it takes the DTM manager to find the node handle:

http://javasourcecode.org/html/open-source/jdk/jdk-6u23/com/sun/org/apache/xml/internal/dtm/ref/dom2dtm/DOM2DTM.html#getHandleOfNode%28org.w3c.dom.Node%29

If the node in question is at the end of the Document, it can end up walking the entire tree to find the node in question, for each and every query.

This explains why the hack to orphan out the target node works. There should be a way to cache these lookups, but at this point I can't see how.

Robbie Matthews
  • 1,404
  • 14
  • 22
  • Hmm, that's interesting input. I have to profile again, though. It hasn't occurred to me so far that this method had any relevant impact on the overall performance. – Lukas Eder Dec 23 '11 at 09:55
  • 1
    I ran into this recently, and discovered the ridiculously inefficient DTM handle lookup by stepping through it in the debugger. Who would've thought that passing a context node, and an expression that selects a direct child of the context node, would involve walking the entire DOM tree? I rewrote my loading code using StAX instead of XPath and my run time went from 5 hours to half a second. – Wyzard Mar 27 '12 at 01:16
0

To answer your question, vtd-xml is way faster than Jaxen or Xalan) (I would say on average 10x, and 60x has been reported...

vtd-xml-author
  • 3,319
  • 4
  • 22
  • 30
  • Interesting. Can you provide a bit more details to back your claims? While this won't be applicable to my original problem anymore, I'll certainly follow up on your tool! – Lukas Eder Jul 29 '13 at 08:46
  • Well, vtd-xml is a simple tool to download, it takes 10 min to test, go to vtd-xml web site and there are lots of relevant info including benchmark tests comparing to Jaxen, and xalan. Our internal results actually show that jaxen's performance has deteriorated considerably over the past several releases.. – vtd-xml-author Jul 29 '13 at 17:03