XPath.evaluate性能降低(荒谬)在多个调用

我正在尝试使用javax.xml.xpath包在具有多个名称空间的文档上运行XPathexpression式,而且我遇到了一些愚蠢的性能问题。

我的testing文档是从一个真实的生产实例中提取的。 这是约600k的XML。 该文档是一个相当复杂的Atom提要。

我意识到我正在用XPath做的事情可以不用。 然而,在其他的,相当差劲的平台上执行相同的执行情况会更糟糕。 现在,重build我的系统不使用XPath超出了我所能做的时间范围。

我的testing代码是这样的:

void testXPathPerformance() { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(loadTestDocument()); XPathFactory xpf = XPathFactory.newInstance(); XPath xp = xpf.newXPath(); NamespaceContext names = loadTestNamespaces(); //there are 12 namespaces in names. In this example code, I'm using //'samplens' instead of the actual namespaces that my application uses //for simplicity. In my real code, the queries are different text, but //precisely the same complexity. xp.setNamespaceContext(names); NodeList nodes = (NodeList) xp.evaluate("/atom:feed/atom:entry", doc.getDocumentElement(), XPathConstants.NODESET); for(int i=0;i<nodes.getLength();i++) { printTimestamp(1); xp.evaluate("atom:id/text()", nodes.item(i)); printTimestamp(2); xp.evaluate("samplens:fieldA/text()", nodes.item(i)); printTimestamp(3); xp.evaluate("atom:author/atom:uri/text()", nodes.item(i)); printTimestamp(4); xp.evaluate("samplens:fieldA/samplens:fieldB/&at;attrC", nodes.item(i)); printTimestamp(5); //etc. My real example has 10 of these xp.evaluate lines } } 

当我在Nexus One上运行(不是在debugging器中,而是连接了USB)时,第一次通过循环,每个xp.evaluate需要从10ms到20ms。 到第15次循环时,每个xp.evaluate需要从200ms到300ms。 循环结束( nodes有150个项目),每个xp.evaluate需要大约500ms-600ms。

我试过使用xp.compile()。 编译全部都是<5ms。 我已经做了xp.reset()(没有区别)。 我为每个评估做了一个新的XPath对象(增加了约4ms)。

内存使用情况在执行过程中似乎不会失控。

我在一个JUnittesting用例的单线程上运行这个程序,它不会创build任何活动。

我真的很困惑。

有没有人知道还有什么可以尝试的?

谢谢!

更新

如果我向后运行for循环( for(int i=nodes.getLength()-1;i>=0;i--) ),那么前几个节点需要500ms-600ms,最后一个节点要快10ms -20ms。 所以,这看起来好像和调用次数没有任何关系,相反,其上下文接近文档结尾的expression式要比其上下文接近文档开始的expression式要长。

有没有人对我能做些什么有什么想法?

Solutions Collecting From Web of "XPath.evaluate性能降低(荒谬)在多个调用"

尝试在顶部的循环内添加此代码;

 Node singleNode = nodes.item(i); singleNode.getParentNode().removeChild(singleNode); 

然后使用singleNodevariables而不是nodes.item(i);运行每个评估nodes.item(i); (当然你改名字)

这样做会从大的主文档中分离正在使用的节点。 这将大大加快评估方法的处理时间。

EX:

 for(int i=0;i<nodes.getLength();i++) { Node singleNode = nodes.item(i); singleNode.getParentNode().removeChild(singleNode); printTimestamp(1); xp.evaluate("atom:id/text()", singleNode ); printTimestamp(2); xp.evaluate("samplens:fieldA/text()", singleNode ); printTimestamp(3); xp.evaluate("atom:author/atom:uri/text()", singleNode ); printTimestamp(4); xp.evaluate("samplens:fieldA/samplens:fieldB/&at;attrC", singleNode ); printTimestamp(5); //etc. My real example has 10 of these xp.evaluate lines } 

这似乎是另一种情况,使用XPath似乎很慢,而不是XPath,原因可能是由DOM方法nodelist.item(i)引起的,

Java中NodeList的默认实现具有以下特点:

  1. 这是懒惰的评估
  2. DOM列表是活的
  3. 它被实现为一个链表
  4. 该列表有一些caching

当您分别查看这些function时,您可能会想知道为什么XPathexpression式的结果对象具有这样的function,但将它们放在一起时更有意义。

1)懒惰的评估可能会模糊性能瓶颈的位置。 正因为如此,返回NodeList似乎是快速的,但是如果任务总是遍历整个列表,那么它或多或less只会降低性能成本。 如果每次阅读列表中的下一个项目时都必须重新处理整个列表的评估,则懒惰评估变得昂贵。

2) NodeList是一个“活动”列表意味着它被更新,并且指的是当前在文档树中的节点,而不是当列表最初被构build时树中的节点或者克隆这些节点的节点。 这是DOM初学者需要掌握的一个重要特性。 例如,如果select同胞元素的NodeList并尝试向每个节点添加一个新的同级元素,则对item(i+1)采取步骤将始终到达最新添加的节点,并且该循环将永远不会结束。

3)正在使用的列表也给出了一些解释,为什么它被实现为一个链表(或AFAIK的实际实现是一个双向链表)。 这个效果可以清楚的看出,在你的testing中,访问最后一个元素总是最慢的,无论你是向后还是向前迭代。

4)由于caching的原因,如果caching保持干净,则循环遍历一个列表,而不会导致对树进行任何更改。 在某些版本的Java中,这个caching存在问题。 我没有调查什么所有程序无效caching,但可能最安全的赌注是build议保持评估expression式相同,不改变树,一次循环在一个列表,并总是进入下一个或上一个列表项。

当然,真正的性能取决于用例。 不要只是调整列表循环,你应该尝试摆脱循环一个实况列表 – 至less作为参考。 克隆使列表不生存。 直接访问节点可以通过将节点复制到一个数组来实现。 如果结构是合适的,你也可以使用其他的DOM方法,比如getNextSibling() ,它比循环NodeList更有效。

尝试克隆节点(所以你不会有从其祖先不必要的引用)

 Node singleNode = nodes.item(i).clone(true); 

如果你删除了孩子,你将失去引用,只有你想要处理的节点的一半。

这有点晚了,但我遇到了同样的情况,但似乎我的文件太大,其他答案都没有真正解决问题。

最终我find了jaxen 。 一旦我使用它,以前需要15秒parsing的文件只需要几毫秒。

Jaxen不幸地被logging得很糟糕,但工作得很好:

 DOMXPath myXPath = new DOMXPath("atom:id/text()"); String myContent = myXPath.stringValueOf(myDocument); 

Java文档可以在这里findhttp://jaxen.codehaus.org/apidocs/org/jaxen/dom/DOMXPath.html

每次你从一个Node列表中获取一个Node,似乎都会引用xml的整个结构; 因为这个原因,当你浏览节点的时候,xpath进程每次都从xml的根开始,因此,当你在trhee下去的时候需要更多的时间。

出于这个原因,当你拿一个节点的时候,在导航之前,你必须用这个方法来inputstring:

 private String nodeToString(Node node) { StringWriter sw = new StringWriter(); try { Transformer t = TransformerFactory.newInstance().newTransformer(); t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); t.transform(new DOMSource(node), new StreamResult(sw)); } catch (TransformerException te) { System.out.println("nodeToString Transformer Exception"); } return sw.toString(); } 

然后在元素/节点中重新转换它:

 String xml = nodeToString(node); Element nodeNew = DocumentBuilderFactory .newInstance() .newDocumentBuilder() .parse(new ByteArrayInputStream(xml.getBytes())) .getDocumentElement(); node = nodeNew; 

通过这种方式,新的元素,失去了所有对他的祖先的引用,并将被用作一个简单的节点,而不是一个嵌套的节点。 显然,只有在深入到节点的情况下,这种方法才是有效的。