XPath.valutare le prestazioni rallenta (assurdamente) su più chiamate

Sto cercando di utilizzare il javax.xml.xpath pacchetto per eseguire espressioni XPath su un documento con più spazi dei nomi, e sto avendo pippo problemi di prestazioni.

Mio documento di prova è tirato da un vero esempio di realizzazione. Si tratta di circa 600k di xml. Il documento è abbastanza complesso feed Atom.

Mi rendo conto che quello che sto facendo con XPath potrebbe essere fatto senza. Tuttavia, la stessa implementazione di altri, di gran lunga inferiore piattaforme esegue assurdamente meglio. Ora, la ricostruzione, il mio sistema non usare XPath è oltre la portata di quello che posso fare nel tempo che ho.

Mio codice di prova è qualcosa di simile a questo:



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

     }
}

Quando ho eseguito su un Nexus One, (non nel debugger, ma con collegamento USB), la prima volta attraverso il ciclo, ogni xp.valutare prende da qualche parte da 10ms a 20ms. Entro il 15 del tempo attraverso il ciclo, ogni xp.valutare prende da qualche parte da 200ms a 300ms. Entro la fine del loop (ci sono 150 elementi in nodes), ci vogliono circa 500ms-600ms per ogni xp.valutare.

Ho provato con xp.compile(). La compilazione tutti i take <5ms. Ho fatto xp.reset() (non fa differenza). Ho fatto un nuovo XPath oggetto per ogni valuta (aggiunge circa 4ms).

Utilizzo della memoria non viene visualizzato a spirale fuori controllo durante l’esecuzione.

Io sono in esecuzione su un singolo thread in un test JUnit che non crea un’attività o nulla.

Sono davvero perplesso.

Qualcuno ha idea di cosa provare?

Grazie!

aggiornamento

Se ho eseguito il loop indietro (for(int i=nodes.getLength()-1;i>=0;i--)), quindi i primi nodi di prendere la 500ms-600ms, e le ultime andare veloce 10ms-20ms. Quindi, questo è come sembra non ha nulla a che fare con il numero di chiamate, ma, invece, che le espressioni, il cui contesto è vicino alla fine del documento di prendere più di espressioni, il cui contesto è vicino all’inizio del documento.

Qualcuno ha qualche idea su cosa posso fare?

  • Shelansky: hai provato ad eseguire una sola query utilizzare | unione dei nodi oparator? Risultato nodo potrebbe essere nell’ordine del documento.
  • Shelansky: la Mia ipotesi è che la NodeList essere restituito dall’espressione XPath è valutato pigramente. Così ogni volta che si fanno i nodi.voce(i) si trova a dover contare tramite l’ho elementi per trovare il nodo. Provate a memorizzare il nodo della variabile all’inizio del ciclo e vedere se questo aiuta.
  • Jones. Nel mio codice di test, sto facendo pigro eval per i nodi.voce(i). Nel codice di produzione, in realtà sto scorrere i nodi immediatamente dopo la chiamata del primo xp.valutare. Nodi risultanti vengono memorizzati in un hashmap da UUID al Nodo, e valutato in quel modo. Il codice di produzione presenta lo stesso problema. Buona idea, però.
  • Io non posso aiutare, ma volevo dispiaciuti che ‘assurdo’ inoltre descritto la mia esperienza con il tentativo di utilizzare il riferimento javax.xml.xpath in produzione. L’unica vera soluzione per noi è stato il passaggio di tutto per Jaxen. Non so se è possibile anche su android 🙁
  • no, non l’ho fatto. Io in realtà non so in anticipo che cosa il documento di ordine è di andare per gli elementi che voglio. Per quanto posso dire, però, l’unico fattore importante da considerare per quanto tempo ci vorrà per eseguire, per quanto lontano giù nel documento il nodo di contesto è.
  • Io ancora non ho capito il perché di tutto questo, oltre che per essere certi che si tratta di pura su come lontano dalla cima del documento il nodo di contesto è. Per i miei scopi, dato che io sono sempre al lavoro con documenti piuttosto grossi, e mai utilizzando Xpath che preoccupano i genitori o antenati, sto solo chiamando cloneNode() prima di chiamare xp.valutare. Si corre a circa 800% più veloce. Questo è un terribile “soluzione”, perché so che un giorno avrò un’espressione che si preoccupa per il genitore, ma per ora…



5 Replies
  1. 51

    Provare ad aggiungere questo codice all’interno del ciclo in alto;

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

    quindi eseguire ogni valutazione utilizzando il singleNode variabile invece di nodes.item(i);
    (ovviamente si modifica il nome)

    Facendo questo si stacca il nodo che si sta lavorando con il grande documento principale. Questo permetterà di accelerare il valutare i metodi di elaborazione di tempo da una quantità enorme.

    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
    
     }
    • +1 per il distacco della punta. Migliorato il mio codice da diversi minuti, meno di 10 secondi !
    • Sì che fa una differenza enorme.
    • Non ci posso credere, ma lo fa. Nel mio caso, piuttosto che la rimozione del nodo ho clonato e ancora visto una ventina di volte il miglioramento delle prestazioni.
    • È una sorta di mi ha messo sulla strada giusta. Ho fatto qualcosa di simile per la rimozione del nodo, ho clonato esso. Si è ridotto il mio tempo di elaborazione da 12 minuti e 10 secondi. Non sto scherzando.
  2. 13

    Questo sembra essere un altro caso in cui l’utilizzo di XPath sembra essere lento, ma invece di XPath, il motivo è probabilmente causato da DOM metodo nodelist.item(i)

    L’implementazione predefinita di NodeList in Java ha determinate caratteristiche:

    1. È valutato pigramente
    2. DOM elenco è vivere
    3. È implementato come una lista collegata
    4. L’elenco di alcune cache

    Quando si guarda a queste caratteristiche separatamente, si potrebbe chiedere perché il risultato oggetto di un’espressione XPath che hanno una funzione simile, ma hanno più senso quando li metti insieme.

    1)
    Lazy evaluation potrebbe offuscare la posizione di un collo di bottiglia delle prestazioni. A causa di esso, il ritorno NodeList sembra essere veloce, ma se il compito è quello di sempre scorrere l’elenco, più o meno solo posticipa il costo delle prestazioni. Lazy evaluation diventa costoso, se la valutazione dell’intero elenco devono essere trattati di nuovo ogni volta che il prossimo elemento della lista è leggere.

    2)
    NodeList essere un “live” lista significa che è aggiornato e si riferisce ai nodi che sono attualmente nell’albero del documento, non ai nodi nell’albero quando l’elenco è stato inizialmente costruito o cloni di tali nodi. Questa è una caratteristica importante per una comprensione DOM principianti. Per esempio, se si seleziona un NodeList di elementi di pari livello e si tenta di aggiungere un nuovo elemento di pari livello per ogni nodo, fare un passo per item(i+1) raggiungerà sempre la versione più recente aggiunta del nodo e il ciclo non avrà mai fine.

    3)
    L’elenco di vivere dà anche una spiegazione del perché è implementato come una lista collegata (o, per quanto ne so l’effettiva implementazione è una lista doppiamente concatenata). L’effetto di questo può essere visto chiaramente test dove accedono gli ultimi elementi è sempre il più lento, se è possibile scorrere attraverso all’indietro o in avanti.

    4)
    A causa della cache, loop su una sola lista, pur non causando eventuali modifiche all’albero dovrebbe essere abbastanza efficiente, se la cache rimane pulito. In alcune versioni di Java non ha problemi con questa cache. Non ho indagato che tutte le procedure di invalidare la cache, ma probabilmente la scommessa più sicura sarebbe quella di consigli per mantenere l’espressione valutata la stessa, non apportare alcuna modifica alla struttura ad albero, loop su una lista alla volta, e sempre un passo precedente o successivo nella voce di elenco.

    Prestazioni reali vincite dipendono dal caso d’uso, naturalmente. Invece di modificare la lista di loop, si dovrebbe cercare di sbarazzarsi di un loop live elenco del tutto, almeno per riferimento. La clonazione consente l’elenco non vivere. Accesso diretto ai nodi può essere ottenuta copiando i nodi di una matrice. Se la struttura è adatta, è possibile utilizzare anche altri metodi DOM come getNextSibling(), che ha detto di dare risultati più efficaci che iterare su una NodeList.

    • Grande risposta. Mi piacerebbe vedere alcuni esempi di codice – come si fa a clonare una lista di nodo, che cosa è il modo più rapido per trasformarlo in un array di nodi, ecc?
  3. 6

    Provare la clonazione del nodo (in modo da non avere riferimenti inutili dai suoi antenati)

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

    Se si rimuove i bambini, si perdono i riferimenti e ottenere solo la metà dei nodi che si desidera elaborare.

    • Ho usato questo per il parsing dei messaggi in arrivo, dove il modo più ovvio era irrimediabilmente inadeguato. L’aumento di velocità è ridicolo e inaspettato.
  4. 0

    Questo è un po ‘ in ritardo, ma mi sono imbattuto nella stessa situazione, ma sembrava che il mio documento è stato così grande che nessuna delle altre risposte davvero risolto il problema.

    Alla fine ho trovato la jaxen. Una volta l’ho usato, il documento, che in precedenza ha preso 15 secondi per analizzare preso mera millisecondi.

    Jaxen è, purtroppo, piuttosto mal documentato, ma ha funzionato abbastanza bene:

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

    Java Doc può essere trovato qui http://jaxen.codehaus.org/apidocs/org/jaxen/dom/DOMXPath.html

    • Come di questa scrittura, i link sono morti.
  5. 0

    Ogni volta che si prende un Nodo da una Nodelist, sembra che mantenere i riferimenti a tutta la struttura dell’xml; per questo motivo
    quando si passa il nodo, xpath processo di avvio ogni volta dalla radice di xml, e per questo motivo, quando si va in trhee
    ci vuole più tempo.

    Per questo motivo, quando si prende un nodo, prima di andare, dovete lanciare in stringa con questo metodo:

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

    e poi si ritrasforma in un Elemento /Nodo:

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

    In questo modo l’Elemento nuovo, ha perso tutti i riferimenti ai suoi antenati, e sarà utilizzato come un semplice Nodo e non annidamento di Nodo.
    Ovviamente questo metodo è utile solo se devi navigare in profondità in un nodo.

Lascia un commento