And*_*sky 22 java performance xpath android
我正在尝试使用javax.xml.xpath包在具有多个名称空间的文档上运行XPath表达式,并且我遇到了愚蠢的性能问题.
我的测试文档来自一个真实的生产示例.它大约是600k的xml.该文档是一个相当复杂的Atom提要.
我意识到我正在使用XPath做的事情可以在没有的情况下完成.然而,在其他非常低劣的平台上实现相同的实现表现得非常好.现在,重建我的系统不使用XPath超出了我所能做的范围.
我的测试代码是这样的:
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
}
}
Run Code Online (Sandbox Code Playgroud)
当我在Nexus One上运行时(不在调试器中,但连接了USB),第一次通过循环时,每个xp.evaluate需要10ms到20ms.到第15次循环时,每个xp.evaluate需要200ms到300ms.在循环结束时(有150个项目nodes),每个xp.evaluate需要大约500ms-600ms.
我尝试过使用xp.compile().编译全部花费<5ms.我已经完成了xp.reset()(没有任何区别).我为每个评估做了一个新的XPath对象(增加大约4ms).
在执行期间,内存使用似乎不会失控.
我在JUnit测试用例中的单个线程上运行它,它不会创建任何活动.
我真的很困惑.
有没有人知道还有什么可以尝试?
谢谢!
更新
如果我向后运行for循环(for(int i=nodes.getLength()-1;i>=0;i--)),那么前几个节点需要500ms-600ms,最后几个节点快速运行10ms-20ms.因此,这似乎与调用的数量无关,而是上下文接近文档末尾的表达式比上下文接近文档开头的表达式花费更长的时间.
对于我能做些什么,有没有人有任何想法?
小智 52
尝试在顶部的循环中添加此代码;
Node singleNode = nodes.item(i);
singleNode.getParentNode().removeChild(singleNode);
Run Code Online (Sandbox Code Playgroud)
然后使用singleNode变量而不是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
}
Run Code Online (Sandbox Code Playgroud)
jas*_*sso 13
这似乎是另一种情况,使用XPath看起来很慢但不是XPath,原因可能是由DOM方法引起的 nodelist.item(i)
NodeListJava中的默认实现具有以下特定功能:
当您单独查看这些功能时,您可能想知道为什么XPath表达式的结果对象具有这样的功能,但是当您将它们组合在一起时它们会更有意义.
1) 延迟评估可能会模糊性能瓶颈的位置.因此,返回NodeList似乎很快,但如果任务要总是遍历列表,那么它或多或少只会延迟性能成本.如果每次读取列表中的下一个项目时必须再次处理整个列表的评估,则延迟评估变得昂贵.
2)
NodeList作为"实时"列表意味着它被更新并且引用当前在文档树中的节点,而不是指最初构建列表时树中的节点或者那些节点的克隆.这是掌握DOM初学者的重要特征.例如,如果您选择一个NodeList兄弟元素并尝试向每个节点添加一个新的兄弟元素,则采取步骤item(i+1)将始终到达最新添加的节点,并且循环将永远不会完成.
3) 实时列表还给出了为什么它被实现为链表(或AFAIK实际实现是双向链表)的一些解释.在您的测试中可以清楚地看到这种效果,其中访问最后一个元素始终是最慢的,无论您是通过向后还是向前迭代它.
4) 由于缓存,如果缓存保持干净,循环在单个列表上而不对树进行任何更改应该是相当有效的.在某些Java版本中,此缓存存在问题.我没有调查所有程序使缓存无效但可能最安全的赌注是建议保持评估的表达式相同,不对树进行更改,一次循环一个列表,并始终步进到下一个或上一个列表项.
当然,真正的性能取决于用例.而不是仅仅调整列表循环,你应该尝试完全摆脱循环列表 - 至少作为参考.克隆使列表无法生效.可以通过将节点复制到阵列来实现对节点的直接访问.如果结构合适,您还可以使用其他DOM方法getNextSibling(),这些方法可以提供比循环NodeList更有效的结果.
小智 6
尝试克隆节点(这样你的祖先就不会有不必要的引用)
Node singleNode = nodes.item(i).cloneNode(true);
Run Code Online (Sandbox Code Playgroud)
如果删除子项,则会丢失引用,只会获得要处理的一半节点.
| 归档时间: |
|
| 查看次数: |
13569 次 |
| 最近记录: |