java 遅い - XPath.evaluateのパフォーマンスが複数の呼び出しで低下する(不条理に)




使い方 (5)

Nodelistからノードを取得するたびに、XMLの全体構造への参照が保持されているようです。 この理由から、ノードをナビゲートすると、xpathのプロセスはxmlのルートから毎回開始されます。このため、trheeを使用すると時間がかかります。

このような理由から、ノードを移動する前に、このメソッドで文字列をキャストする必要があります。

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;

このようにして、新しい要素は、祖先への参照をすべて失い、単純なノードとして使用され、入れ子のノードとしては使用されません。 明らかに、このメソッドは、ノードに深く移動する必要がある場合にのみ有効です。

私は、複数の名前空間を持つドキュメント上でXPath式を実行するためにjavax.xml.xpathパッケージを使用しようとしています。私はパフォーマンス上の問題を抱えています。

私のテスト文書は実際の生産例から引き出されています。 それはxmlの約600kです。 この文書は非常に複雑な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

     }
}

私がNexus One(デバッガではなく、USBを接続した状態)で実行すると、最初のループで、各xp.evaluateは10msから20msのどこかに移動します。 ループを通る15回目までに、各xp.evaluateは200msから300msのどこかにかかります。 ループの終わり( nodesには150の項目があります)では、各xp.evaluateに約500ms-600msかかります。

私はxp.compile()を使って試しました。 コンパイルはすべて<5msです。 私はxp.reset()を行った(違いはない)。 私は、各評価のために新しいXPathオブジェクトを作成しました(約4ms追加します)。

実行中にメモリ使用量が制御不能になっているように見えることはありません。

私は、JUnitのテストケース内でアクティビティなどを作成しない単一のスレッドでこれを実行しています。

私は本当に困惑しています。

他に何をしようと考えている人はいますか?

ありがとう!

更新

forループを逆方向に実行すると(最初のいくつかのノードは500ms-600msをとり、最後のノードは10ms高速になります) -20ms。 だから、これは呼び出し回数とは関係ないようですが、文脈が文書の終わり近くにある式は、文脈が文書の先頭近くにある式よりも時間がかかります。

誰も私がこれについて何をすることができるかについての考えを持っていますか?


これは、XPathの使用が遅いが、XPathではなくDOMのnodelist.item(i)によって引き起こされた別のケースのようです。

JavaのNodeListのデフォルトの実装には、特定の機能があります。

  1. それは遅れて評価される
  2. DOMリストはライブです
  3. リンクリストとして実装されています
  4. リストにはいくつかのキャッシングがあります

これらの機能を別々に見ると、XPath式の結果オブジェクトにそのような機能がなければならないのだろうかと疑問に思うかもしれませんが、XPath式を組み合わせると意味があります。

1)レイジー評価は、パフォーマンスのボトルネックの場所をぼかす可能性があります。 そのため、NodeListを返すのは速いようですが、タスクが常にリストを反復するのであれば、多かれ少なかれパフォーマンスのコストを抑えることになります。 リスト内の次の項目が読み取られるたびに、リスト全体の評価を再度処理する必要がある場合、レイジー評価はコストがかかります。

2) NodeListが「ライブ」リストであるということは、それが更新されており、リストが最初に構築されたときにツリー内にあったノードやそれらのノードのクローンではなく、ドキュメントツリーに現在あるノードを指すことを意味する。 これはDOM初心者のための重要な機能です。 たとえば、兄弟要素のNodeListを選択し、各ノードに1つの新しい兄弟要素を追加しようとすると、 item(i+1)へのステップは常に最新の追加ノードに到達し、ループは終了しません。

3)実際にリンクされているリストには、リンクされたリスト(または実際の実装が二重にリンクされているリスト)として実装されている理由が説明されています。 この効果は、バックエンドまたはフォワードを繰り返しても、最後の要素へのアクセスが常に最も遅いテストではっきりと確認できます。

4)キャッシングのために、ツリーを変更しないで1つのリストをルーピングすると、キャッシュがクリーンなままであれば、かなり効率的になります。 Javaのいくつかのバージョンでは、このキャッシュに問題がありました。 私はすべてのプロシージャがキャッシュを無効にするのか調査していませんが、評価された式を同じに保ち、ツリーに変更を加えず、一度に1つのリストをループし、常に次または前のリスト項目にステップするアドバイスが最も安全な方法でしょう。

実際のパフォーマンスは、もちろんユースケースに依存します。 リストのループを微調整するのではなく、少なくとも参照のために、ライブリスト全体をループするのをやめてください。 クローニングは、リストを生きていないものにします。 ノードへの直接アクセスは、ノードをアレイにコピーすることによって達成できます。 構造が適切な場合は、 getNextSibling()などのDOMメソッドを使用して、NodeListをループするより効果的な結果を得ることもできます。


これはもう少し遅いですが、私は同じ状況に遭遇しましたが、私の文書が非常に大きく、他の回答のどれもが本当に問題を解決していないようでした。

結局、私はジャクセンを見つけました 。 いったんそれを使用すると、これまでパースするのに15秒かかっていた文書は数ミリ秒しかかかりませんでした。

残念ながら、Jaxenはかなり悪いことに文書化されていますが、かなりうまく機能しました。

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

Java Docはhttp://jaxen.codehaus.org/apidocs/org/jaxen/dom/DOMXPath.html


このコードを最上部のループ内に追加してみてください。

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

nodes.item(i);ではなく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

 }

誰でも私に良い例を教えてくれますか?CROSS APPLYは、INNER JOINも同様に動作するケースに違いがありますか?

パフォーマンスの比較の詳細については、私のブログの記事を参照してください。

CROSS APPLYは、単純なJOIN条件を持たないものの方が効果的です。

これは、 t1から各レコードのt2から3最後のレコードを選択します。

SELECT  t1.*, t2o.*
FROM    t1
CROSS APPLY
        (
        SELECT  TOP 3 *
        FROM    t2
        WHERE   t2.t1_id = t1.id
        ORDER BY
                t2.rank DESC
        ) t2o

INNER JOIN条件では簡単に定式化できません。

おそらく、 CTEの関数とウィンドウ関数を使って次のようなことをすることができます:

WITH    t2o AS
        (
        SELECT  t2.*, ROW_NUMBER() OVER (PARTITION BY t1_id ORDER BY rank) AS rn
        FROM    t2
        )
SELECT  t1.*, t2o.*
FROM    t1
INNER JOIN
        t2o
ON      t2o.t1_id = t1.id
        AND t2o.rn <= 3

しかし、これは読みにくく、おそらく効率が悪いでしょう。

更新:

ちょうどチェックした。

masterid PRIMARY KEYを持つ約20,000,000レコードのテーブルです。

このクエリ:

WITH    q AS
        (
        SELECT  *, ROW_NUMBER() OVER (ORDER BY id) AS rn
        FROM    master
        ),
        t AS 
        (
        SELECT  1 AS id
        UNION ALL
        SELECT  2
        )
SELECT  *
FROM    t
JOIN    q
ON      q.rn <= t.id

これはほぼ30秒間実行されます。

WITH    t AS 
        (
        SELECT  1 AS id
        UNION ALL
        SELECT  2
        )
SELECT  *
FROM    t
CROSS APPLY
        (
        SELECT  TOP (t.id) m.*
        FROM    master m
        ORDER BY
                id
        ) q

即刻です。





java android performance xpath