2010-09-23 11 views
20

Próbuję użyć pakietu javax.xml.xpath do uruchamiania wyrażeń XPath w dokumencie z wieloma przestrzeniami nazw i mam niemądre problemy z wydajnością.XPath. Ewaluuj wydajność spowalnia (absurdalnie) przy wielu połączeniach

Mój dokument testowy jest wyciągany z prawdziwego, przykładowego przykładu. To około 600k xml. Dokument jest dość złożonym kanałem Atom.

Zdaję sobie sprawę, że to, co robię z XPath, można zrobić bez. Jednak ta sama implementacja na innych, znacznie gorszych platformach działa absurdalnie lepiej. Teraz, odbudowanie mojego systemu, aby nie używać XPath, wykracza poza zakres moich działań w tym czasie, jaki mam.

Mój kod test jest coś takiego:



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 

    } 
} 

Kiedy biegnę na Nexus One, (nie w debugger, ale z podłącz.USB), po raz pierwszy za pomocą pętli, każda xp.evaluate trwa gdzieś od 10ms do 20ms. Po 15 raz w pętli, każda ocena xp.e zatrudnia gdzieś od 200ms do 300ms. Pod koniec pętli (150 pozycji w nodes) trwa około 500ms-600ms dla każdego xp.evaluate.

Próbowałem już używać xp.compile(). Wszystkie kompilacje zabierają < 5 ms. Zrobiłem xp.reset() (nie robi różnicy). Zrobiłem nowy obiekt XPath dla każdej oceny (dodaje około 4ms).

Wydaje się, że użycie pamięci nie ma wpływu na kontrolę podczas wykonywania.

Uruchomiłem to na pojedynczym wątku w teście JUnit, który nie tworzy żadnego działania.

Jestem naprawdę zaintrygowany.

Czy ktoś ma pojęcie, co jeszcze można wypróbować?

Dzięki!

aktualizacja

Jeśli biegnę do pętli wstecznej (for(int i=nodes.getLength()-1;i>=0;i--)), następnie kilka pierwszych węzłów podejmuje 500ms-600ms, a te ostatnie iść szybko 10ms-20ms. Wydaje się więc, że nie ma to nic wspólnego z liczbą wywołań, ale zamiast tego wyrażenia, których kontekst znajduje się blisko końca dokumentu, trwają dłużej niż wyrażenia, których kontekst znajduje się blisko początku dokumentu.

Czy ktoś ma jakiekolwiek przemyślenia na temat tego, co mogę z tym zrobić?

+0

@Andrew Shelansky: Czy próbowałeś uruchomić tylko jedno zapytanie używając '|' oporu zbioru węzłów? Zestaw węzłów wyników będzie w porządku dokumentu. –

+1

@Andrew Shelansky: Domyślam się, że wartość NodeList zwracana przez wyrażenie XPath jest oceniana leniwie. Tak więc za każdym razem, gdy robisz nodes.item (i), musisz liczyć przez i przedmioty, aby znaleźć węzeł. Spróbuj zapisać węzeł w zmiennej na początku pętli i sprawdź, czy to pomaga. –

+0

@Nick Jones. W moim kodzie testowym robię leniwy eval dla nodes.item (i). W moim kodzie produkcyjnym, właśnie iteruję przez węzły natychmiast po wywołaniu pierwszego xp.evaluate. Wynikowe węzły są przechowywane w mapie mieszającej od UUID do węzła i oceniane w ten sposób. Kod produkcyjny wykazuje ten sam problem. Dobra myśl. –

Odpowiedz

9

Wydaje się to być kolejny przypadek, w którym za pomocą XPath wydaje się powolny, ale zamiast XPath, powodem jest prawdopodobnie spowodowane metodą DOM nodelist.item(i)

Domyślna implementacja NodeList w Javie ma pewne cechy:

  1. to jest oceniany leniwie
  2. lista DOM jest żywo
  3. jest on realizowany jako połączonej listy
  4. Lista ma pewne buforowanie

Kiedy patrzysz na tych cech osobno, można się zastanawiać, dlaczego obiekt wynikiem wyrażenia XPath posiada funkcję takiego, ale więcej sensu, kiedy można umieścić je razem .

1) Leniwa ocena może zacierać położenie wąskiego gardła wydajności. Z tego powodu zwracanie NodeList wydaje się być szybkie, ale jeśli zadaniem jest zawsze iterować listę, to mniej więcej po prostu odrzuca koszt wydajności. Leniwa ocena staje się kosztowna, jeśli ocena całej listy musi być ponownie przetworzona za każdym razem, gdy czytany jest następny element na liście.

2) NodeList bycia lista „na żywo” oznacza, że ​​jest ona aktualizowana i odnosi się do węzłów, które są aktualnie w drzewie dokumentu, a nie do węzłów, które były w drzewie, gdy lista została pierwotnie skonstruowany lub do klonów tych węzłów. Jest to ważna cecha dla początkujących DOM. Na przykład, jeśli wybierzesz NodeList elementów rodzeństwa i spróbujesz dodać jeden nowy element siostrzany do każdego węzła, zrobienie kroku do item(i+1) zawsze dotrze do ostatnio dodanego węzła, a pętla nigdy się nie skończy.

3) Lista jest na żywo daje również pewne wyjaśnienie dlaczego jest zaimplementowany jako połączonej listy (lub AFAIK faktyczna realizacja jest podwójnie związany lista). Efekt tego można wyraźnie zobaczyć na teście, gdy dostęp do ostatnich elementów jest zawsze najwolniejszy, niezależnie od tego, czy robisz to w tył czy w przód.

4) Ze względu na buforowanie, zapętlenie nad jednym liście natomiast nie powoduje żadnych zmian w drzewie powinien być dość skuteczny, jeśli bufor pozostaje czysty. W niektórych wersjach Java wystąpiły problemy z tym buforowaniem. Nie badałem, jakie procedury unieważniają buforowanie, ale prawdopodobnie najbezpieczniej byłoby doradzić, aby zachowane wyrażenie było takie samo, nie wprowadzać żadnych zmian w drzewie, pętli po jednej liście na raz i zawsze przechodzić do następnego lub poprzedniego elementu listy.

Prawdziwe wygrane wydajności zależą oczywiście od zastosowania. Zamiast ulepszać pętlę listy, powinieneś spróbować całkowicie wyłączyć pętlę listy na żywo - przynajmniej w celach informacyjnych. Klonowanie powoduje, że lista nie jest aktywna. Bezpośredni dostęp do węzłów można uzyskać, kopiując węzły do ​​tablicy. Jeśli struktura jest odpowiednia, można również użyć innych metod DOM, takich jak getNextSibling(), które powiedziały, że dają bardziej efektywne wyniki niż pętla nad NodeList.

+2

Świetnie odpowiedź. Chciałbym zobaczyć kilka przykładów kodu - jak klonować listę węzłów, jaki jest najszybszy sposób przekształcenia go w tablicę węzłów, itd.? –

46

Spróbuj dodać ten kod w pętli u góry;

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

następnie uruchomić każdą ocenę użyciu zmiennej singleNode zamiast nodes.item(i); (oczywiście po zmianie nazwy)

ten sposób odłącza węzeł pracujesz z od dużego dokumentu głównego. Przyspieszy to czas przetwarzania metod o ogromną ilość.

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 

} 
+4

+1 za końcówkę odłączającą. Poprawiłem mój kod z kilku minut do mniej niż 10 sekund! – Adam

+3

Tak, to robi wielką różnicę. – Lee

+4

Nie mogę uwierzyć, że to działa, ale tak. W moim przypadku, zamiast usunąć węzeł, sklonowałem go i nadal widziałem dwudziestokrotną poprawę wydajności. – CurtainDog

0

To jest trochę późno, ale wpadłem na tej samej sytuacji, ale wydawało się, że mój dokument był tak duży, że żaden z pozostałych odpowiedzi naprawdę rozwiązać problem.

W końcu znalazłem jaxen. Kiedyś go użyłem, dokument, który wcześniej zajmował 15 sekund w celu przeanalizowania, zajmował zaledwie milisekundy.

Jaxen jest niestety raczej źle udokumentowane, ale działało całkiem dobrze:

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

Java Doc można znaleźć tutaj http://jaxen.codehaus.org/apidocs/org/jaxen/dom/DOMXPath.html

2

Spróbuj klonowania węzeł (więc nie będzie miał niepotrzebnych referencje od jego przodkowie)

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

Jeśli usuniesz dzieci, stracisz referencje i dostać tylko połowę z węzłów, które mają być przetwarzane.

0

Za każdym razem, gdy weźmiesz węzeł od Nodelisty, wydaje się, że zachowuje odniesienia do całej struktury xml; z tego powodu podczas nawigowania po węźle proces xpath rozpoczyna się za każdym razem od katalogu głównego xml, i z tego powodu, kiedy zejdziesz na dół w trhee , zajmuje to więcej czasu.

Z tego powodu jeśli wziąć węzeł przed nawigować, musisz rzucać w ciąg tą metodą:

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(); 
     } 

a następnie retransform go w elemencie/node:

String xml = nodeToString(node); 

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

node = nodeNew; 

W ten sposób nowy element utracił wszystkie odniesienia do swoich przodków i będzie użyty jako prosty węzeł, a nie jako zagnieżdżony węzeł. Oczywiście ta metoda jest dobra tylko wtedy, gdy musisz nawigować w głąb węzła.

Powiązane problemy