XPath.évaluer la performance ralentit (par l'absurde) sur les appels multiples
Je suis en train d'utiliser le javax.xml.xpath package pour exécuter des expressions XPath sur un document avec plusieurs espaces de noms, et je vais avoir dingo des problèmes de performances.
Mon test de document est tiré à partir d'un réel, exemple de production. Il est d'environ 600k de xml. Le document est assez complexe Atom.
Je me rends compte que ce que je fais avec XPath qui pourrait être fait sans. Cependant, la même mise en œuvre sur d'autres, largement inférieure plates-formes effectue par l'absurde mieux. Maintenant, la reconstruction de mon système de ne pas utiliser XPath est au-delà de la portée de ce que je peux faire dans le temps que j'ai.
Mon code de test est quelque chose comme ceci:
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
}
}
Quand je le lance sur un Nexus One, (pas dans le débogueur, mais avec l'USB connecté), le premier passage dans la boucle, chaque xp.évaluer prend quelque part de 10ms à 20ms. Par la 15e fois dans la boucle, chaque xp.évaluer prend quelque part à partir de 200 ms à 300 ms. D'ici à la fin de la boucle (il y a 150 articles dans nodes
), il faut environ 500ms-600ms pour chaque xp.évaluer.
J'ai essayé d'utiliser xp.compiler(). La compile tous les prendre <5ms. J'ai fait xp.reset() (ne fait pas de différence). J'ai fait une nouvelle XPath objet à évaluer (ajoute à propos de 4ms).
L'utilisation de la mémoire ne semble pas en spirale hors de contrôle pendant l'exécution.
Je suis l'exécution de ce sur un seul thread dans un cas de test JUnit qui n'est pas de créer une activité ou de quoi que ce soit.
Je suis vraiment perplexe.
Quelqu'un a une idée ce que d'autre à essayer?
Merci!
mise à jour
Si j'exécute la boucle vers l'arrière (for(int i=nodes.getLength()-1;i>=0;i--)
), puis les premiers nœuds de prendre les 500ms-600ms, et les derniers à aller vite 10ms-20ms. Donc, cela semble comme il n'a rien à voir avec le nombre d'appels, mais plutôt que les expressions dont le contexte est proche de la fin du document, de prendre plus de temps que les expressions dont le contexte est près du début du document.
Quelqu'un avez des idées sur ce que je peux faire à ce sujet?
- Shelansky: Avez-vous essayez d'exécuter une seule requête de ussing
|
de l'union de l'ensemble de nœuds oparator? Résultat de l'ensemble de nœuds serait dans l'ordre du document. - Shelansky: j'imagine que la NodeList être de retour par l'expression XPath est évaluée paresseusement. Donc, chaque fois que vous faites des nœuds.le point(i) il est d'avoir à compter, je les éléments de trouver le nœud. Essayez de stocker le nœud de la variable au début de la boucle et voir si cela aide.
- Jones. Dans mon code de test, je suis en train de faire paresseux eval pour les nœuds.le point(i). Dans mon code de production, je suis en fait une itération à travers les nœuds immédiatement après l'appel de la première xp.évaluer. Les nœuds qui en résultent sont stockés dans une table de hachage de l'UUID de Nœud, et de l'évaluation de cette façon. Le code de production présente le même problème. Bien pensé, bien.
- Je ne peux pas aider, mais je voulais témoigner leur sympathie qu' '"absurde" décrit également mon expérience avec le fait d'essayer d'utiliser la référence javax.xml.xpath dans la production. La seule véritable solution pour nous a été de commutation tout à Jaxen. Ne sais pas si c'est encore possible sur android 🙁
- non, je ne l'ai pas fait. Je n'ai pas vraiment savoir à l'avance ce que l'ordre du document va être pour les éléments que je veux. Aussi loin que je peux dire, cependant, la seule considération importante pour combien de temps il faudra pour exécuter est de savoir comment loin dans le document, le nœud de contexte est.
- Je n'ai toujours pas compris pourquoi ce qui se passe, sauf pour être certain que c'est purement à propos la distance entre le haut du document, le nœud de contexte est. Pour ma part, depuis que je suis toujours en train de travailler avec des documents plutôt gros, et jamais à l'aide de Xpath qui se soucient de parents ou ancêtres, je suis juste appeler cloneNode() avant d'appeler xp.évaluer. Il fonctionne environ 800% plus rapide. C'est un terrible "solution" parce que je sais qu'un jour je vais avoir une expression qui se soucie de la mère, mais pour l'instant...
Vous devez vous connecter pour publier un commentaire.
Essayez d'ajouter ce code à l'intérieur de la boucle sur le dessus;
puis exécutez chaque évaluation à l'aide de la
singleNode
variable au lieu denodes.item(i);
(bien sûr, vous changez le nom)
Faire ce détache le nœud que vous travaillez avec de grandes document principal. Cela permettra d'accélérer la vitesse d'évaluer les méthodes de temps de traitement par une quantité énorme.
EX:
Ce qui semble être un autre cas où l'aide de XPath semble être lente, mais au lieu de XPath, la raison en est probablement causée par une méthode DOM
nodelist.item(i)
Le défaut de mise en œuvre de
NodeList
en Java a certaines caractéristiques:Quand vous regardez ces fonctions séparément, vous pourraient se demander pourquoi l'objet de résultat d'une expression XPath ont une fonction comme ça, mais ils font plus de sens quand vous les mettez ensemble.
1)
Paresseux évaluation du risque de brouiller la l'emplacement d'un goulot d'étranglement des performances. À cause de cela, le retour de la NodeList semble être rapide, mais si la tâche est toujours de parcourir la liste, plus ou moins juste confie le coût de performance. Évaluation différée devient cher, si l'évaluation de l'ensemble de la liste doivent être traitées de nouveau à chaque fois lorsque l'élément suivant dans la liste de lecture.
2)
NodeList
être un "live" de la liste signifie qu'il est mis à jour et se réfère à des nœuds qui sont actuellement dans l'arborescence du document, pas de nœuds dans l'arbre quand la liste a été initialement construit ou à des clones de ces nœuds. C'est une caractéristique importante à saisir pour les DOM les débutants. Par exemple, si vous sélectionnez unNodeList
des éléments frères et essayez d'ajouter un nouvel élément frère à chaque nœud, en prenant un peu deitem(i+1)
sera toujours atteindre le dernier nœud ajouté et la boucle ne sera jamais fini.3)
La liste étant live donne aussi quelques explications pourquoi il est implémenté comme une liste chaînée (ou autant que je sache, la mise en œuvre effective est une liste doublement chaînée). L'effet de ce qui peut être clairement vu sur votre test où l'accès au dernier élément est toujours le plus lent, si vous de le parcourir à travers vers l'arrière ou vers l'avant.
4)
En raison de la mise en cache, en boucle sur une liste unique tout en ne causant pas de toute modification de l'arbre doit être assez efficace, si le cache reste propre. Dans certaines versions de Java il y a eu des problèmes avec cette mise en cache. Je n'ai pas étudié ce que toutes les procédures d'invalider le cache, mais probablement le plus sûr de paris seraient de conseils à garder à l'expression évaluée de la même, n'apporter aucune modification à l'arbre, de la boucle sur une seule liste à la fois, et toujours à l'étape suivante ou précédente de la liste de l'élément.
Performances réelles victoires dépendent de l'utilisation de cas, bien sûr. Au lieu de simplement modifier la liste en boucle, vous devriez essayer de se débarrasser de la boucle à un direct de la liste - là, au moins pour la référence. Le clonage fait la liste de ne pas vivre. Accès Direct aux nœuds peut être réalisé en copiant les nœuds d'un tableau. Si la structure est adaptée, vous pouvez également utiliser d'autres méthodes du DOM comme
getNextSibling()
qui dit de donner des résultats plus efficaces que boucler sur une NodeList.Essayer de cloner le nœud (de sorte que vous n'aurez pas les références inutiles de ses ancêtres)
Si vous supprimez les enfants, vous allez perdre des références et que la moitié des nœuds que vous souhaitez traiter.
C'est un peu en retard, mais j'ai couru dans la même situation, mais il semblait que mon document était si grand qu'aucune des autres réponses vraiment résolu le problème.
Finalement, j'ai trouvé jaxen. Une fois que je l'ai utilisé, le document qui, auparavant, a pris 15 secondes pour analyser pris quelques millisecondes.
Jaxen est malheureusement assez mal documenté, mais il a travaillé très bien:
La Java Doc peut être trouvé ici http://jaxen.codehaus.org/apidocs/org/jaxen/dom/DOMXPath.html
Chaque fois que vous prenez un Nœud à partir d'une Nodelist, il semble que garder les références à l'ensemble de la structure du xml; pour cette raison
lorsque vous naviguez sur le nœud, le xpath processus commence à chaque fois à partir de la racine de xml, et pour cette raison, lorsque vous descendez dans la trhee
il prend plus de temps.
Pour cette raison, lorsque vous prenez un nœud, avant de naviguer, vous devez lancer en chaîne par cette méthode:
et puis ritrasforma dans un Élément de /Node:
De cette façon, l'Élément nouveau, la perte de toutes les références à ses ancêtres, et de l'utiliser comme un simple Nœud et non pas comme un Nœud imbriqué.
Évidemment, cette méthode n'est bonne que si vous devez naviguer en profondeur dans un nœud.