How to Speed Up Apache Xalan’s XPath Processor by Factor 10x
There has been a bit of an awkward bug in Apache Xalan for a while now, and that bug is XALANJ-2540. The effect of this bug is that an internal SPI configuration file is loaded by Xalan thousands of times per XPath expression evaluation, which can be measured easily as such:
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);
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 configuration file:
META-INF/service/org.apache.xml.dtm.DTMManager
Every time!:
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 c.s.o.a.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
Resources
The above text is an extract from a Stack Overflow question and answer that I’ve contributed to the public a while ago. I’m posting it again, here on my blog, such that the community’s awareness for this rather heavy bug can be raised. Feel free to upvote on this ticket here, as every Sun/Oracle JDK on this planet is affected: https://issues.apache.org/jira/browse/XALANJ-2540
Contributing a fix to Apache would be even better, of course…