2012-12-12 22 views
5

私はXML::Twigを使用して非常に大きなXML文書を解析しています。私は<change></change>タグに基づいてそれをチャンクに分割したいと思います。XML :: Twigを高速化するには

今私が持っている:それはXMLからそのブロックを引っ張ると

my $xml = XML::Twig->new(twig_handlers => { 'change' => \&parseChange, }); 
$xml->parsefile($LOGFILE); 

sub parseChange { 

    my ($xml, $change) = @_; 

    my $message = $change->first_child('message'); 
    my @lines = $message->children_text('line'); 

    foreach (@lines) { 
    if ($_ =~ /[^a-zA-Z0-9](?i)bug(?-i)[^a-zA-Z0-9]/) { 
     print outputData "$_\n"; 
    } 
    } 

    outputData->flush(); 
    $change->purge; 
} 

は今のところ、これはparseChangeメソッドを実行しています。非常に遅いです。私は、$/=</change>というファイルからXMLを読み込み、XMLタグの内容を返す関数を書くことに対してそれをテストしました。

何かがありませんか?XML::Twigを間違って使用していますか?私はPerlの初心者です。

EDIT:変更ファイルからの変更例です。ファイルは、他の後に、これらの1つの右の多くで構成され、それらの間に何があってはならない。

<change> 
<project>device_common</project> 
<commit_hash>523e077fb8fe899680c33539155d935e0624e40a</commit_hash> 
<tree_hash>598e7a1bd070f33b1f1f8c926047edde055094cf</tree_hash>  
<parent_hashes>71b1f9be815b72f925e66e866cb7afe9c5cd3239</parent_hashes>  
<author_name>Jean-Baptiste Queru</author_name>  
<author_e-mail>[email protected]</author_e-mail>  
<author_date>Fri Apr 22 08:32:04 2011 -0700</author_date>  
<commiter_name>Jean-Baptiste Queru</commiter_name>  
<commiter_email>[email protected]</commiter_email>  
<committer_date>Fri Apr 22 08:32:04 2011 -0700</committer_date>  
<subject>chmod the output scripts</subject>  
<message>   
    <line>Change-Id: Iae22c67066ba4160071aa2b30a5a1052b00a9d7f</line>  
</message>  
<target>   
    <line>generate-blob-scripts.sh</line>  
</target> 
</change> 
+0

XMLを正規表現で前処理してから、 'XML :: Twig'に渡すことをお勧めします。これにより、コードのロバスト性が大幅に低下します。たとえば、コメント内に「」がある場合はどうなりますか?また、XML構文解析がスクリプトを遅くすることであるとは考えにくいです。より多くの情報を提供できますか:ファイルのサイズと処理の種類は? – dan1111

+0

私はどこでも正規表現を使っていません。 1つの方法は小枝を使用し、もう1つはそれを読み込み、それを自分で解析していました。私は全体のスクリプトからこの部分を抜き出したので、実行中の唯一のものです。 また、ファイルサイズは2.3GBです。私はxmlからデータを抽出し、その一部をハッシュに追加しています。 – user1897691

+0

申し訳ありませんが、「正規表現」と言うのは間違いです。私はあなたがそれを解析する前にいくつかの規則(行区切り記号など)を使用してファイルを分割すると、XMLの完全性を破る可能性があることを意味しました。あなたのXMLファイルの大きさはどれくらいですか? – dan1111

答えて

1

XML::Twigは、彼らが表示されるあなたは何もはや捨てない、その後、タグを扱うことが可能な機構を備えていますメモリを解放する必要があります。ここで

は(も多くの有益な情報を持っている)the documentationから取った例である:

my $t= XML::Twig->new(twig_handlers => 
          { section => \&section, 
          para => sub { $_->set_tag('p'); } 
          }, 
         ); 
    $t->parsefile('doc.xml'); 

    # the handler is called once a section is completely parsed, ie when 
    # the end tag for section is found, it receives the twig itself and 
    # the element (including all its sub-elements) as arguments 
    sub section 
    { my($t, $section)= @_;  # arguments for all twig_handlers 
     $section->set_tag('div'); # change the tag name.4, my favourite method... 
     # let's use the attribute nb as a prefix to the title 
     my $title= $section->first_child('title'); # find the title 
     my $nb= $title->att('nb'); # get the attribute 
     $title->prefix("$nb - "); # easy isn't it? 
     $section->flush;   # outputs the section and frees memory 
    } 

数ギガバイトのファイルを扱うときにこれはおそらく、(再び、によるので、必要不可欠になりますドキュメント)は、ファイル全体のサイズをファイルのサイズの10倍にすることができます。

編集:編集した質問に基づいていくつかのコメントがあります。あなたが行をたくさん書いている場合は、あなたが遅くなり、出力ファイルハンドルをフラッシュ

  • :あなたのファイル構造についての詳細を知らなくてもダウンあなたを遅らせるが、ここではいくつか試してみる事があるさまさに明確ではありません。 Perlはパフォーマンス上の理由から特にファイルの書き込みをキャッシュしており、それをバイパスしています。
  • (?i)メカニズムを使用する代わりに、パフォーマンスのペナルティがあると思われるかなり高度な機能で、大文字と小文字の区別を区別しないようにしましょう。 /[^a-z0-9]bug[^a-z0-9]/iは同等です。 /\bbug\b/iで簡略化することもできます。はほぼに相当します。唯一の違いは、一致しないクラスにアンダースコアが含まれていることです。
  • 中間ステップを削除する他の簡単な方法もあります。

このハンドラコードは、あなたのスピードに比べてどのように速度が違いますか?

sub parseChange 
{ 
    my ($xml, $change) = @_; 

    foreach(grep /[^a-z0-9]bug[^a-z0-9]/i, $change->first_child_text('message')) 
    { 
     print outputData "$_\n"; 
    } 

    $change->purge; 
} 
+0

私はこれをちょっと見ましたが、私は「para」行について混乱していることを認めなければなりません。私はこれが私がやっていることだと思います。私のサンプルコードでは、ハンドラを定義したことがわかります。 – user1897691

+0

@ user1897691、あなたはあなたのハンドラ内のメモリを解放するために 'flush'または' purge'しましたか?私は 'XML :: Twig'の専門家ではありませんが、ハンドラのコードを投稿すれば誰かがあなたをもっと助けるかもしれません。 – dan1111

+0

私は元の質問にそれを加えました。誰かがFileIOが高価であることについて誰かがコメントしてくれると確信していますが、コードの両方のバージョンで実行されていて、別のタイミングが得られています。 FileIOは、他のものよりもはるかに速く動作する理由ではありません。 – user1897691

3

それは、あなたのプログラムがあなたに興味を持っていないchange要素外のデータを含め、XML文書のすべてを処理している現状では。

あなたにtwig_handlersパラメータを変更した場合コンストラクタをtwig_rootsに設定すると、ツリー構造は対象の要素に対してのみ構築され、残りは無視されます。

my $xml = XML::Twig->new(twig_roots => { change => \&parseChange }); 
+0

私はこれを試しますが、ドキュメントはちょうどお互いの後にちょうど変更の束でなければなりません。私はそれを実行し始めて、それは以前と同じスピードであるように見えます。 – user1897691

+0

その後、あなたのXMLを['SQLite'](https://metacpan.org/module/DBD::SQLite)にインポートし、そこから作業し、後でそれをエクスポートする必要があります。 XMLはランダムアクセスデータベース形式ではありません。 – Borodin

0

XMLが実際に大きい場合はXML::SAXを使用してください。データセット全体をメモリにロードする必要はありません。代わりに、ファイルを順次ロードし、すべてのタグのコールバックイベントを生成します。私はXML :: SAXを使って、1GBを超えるサイズのXMLを解析しています。ここにあなたのデータのためのXMLの例:: SAXハンドラは次のとおりです。

#!/usr/bin/env perl 
package Change::Extractor; 
use 5.010; 
use strict; 
use warnings qw(all); 

use base qw(XML::SAX::Base); 

sub new { 
    bless { data => '', path => [] }, shift; 
} 

sub start_element { 
    my ($self, $el) = @_; 
    $self->{data} = ''; 
    push @{$self->{path}} => $el->{Name}; 
} 

sub end_element { 
    my ($self, $el) = @_; 
    if ($self->{path} ~~ [qw[change message line]]) { 
     say $self->{data}; 
    } 
    pop @{$self->{path}}; 
} 

sub characters { 
    my ($self, $data) = @_; 
    $self->{data} .= $data->{Data}; 
} 

1; 

package main; 
use strict; 
use warnings qw(all); 

use XML::SAX::PurePerl; 

my $handler = Change::Extractor->new; 
my $parser = XML::SAX::PurePerl->new(Handler => $handler); 

$parser->parse_file(\*DATA); 

__DATA__ 
<?xml version="1.0"?> 
<change> 
    <project>device_common</project> 
    <commit_hash>523e077fb8fe899680c33539155d935e0624e40a</commit_hash> 
    <tree_hash>598e7a1bd070f33b1f1f8c926047edde055094cf</tree_hash> 
    <parent_hashes>71b1f9be815b72f925e66e866cb7afe9c5cd3239</parent_hashes> 
    <author_name>Jean-Baptiste Queru</author_name> 
    <author_e-mail>[email protected]</author_e-mail> 
    <author_date>Fri Apr 22 08:32:04 2011 -0700</author_date> 
    <commiter_name>Jean-Baptiste Queru</commiter_name> 
    <commiter_email>[email protected]</commiter_email> 
    <committer_date>Fri Apr 22 08:32:04 2011 -0700</committer_date> 
    <subject>chmod the output scripts</subject> 
    <message> 
    <line>Change-Id: Iae22c67066ba4160071aa2b30a5a1052b00a9d7f</line> 
    </message> 
    <target> 
    <line>generate-blob-scripts.sh</line> 
    </target> 
</change> 

出力

Change-Id: Iae22c67066ba4160071aa2b30a5a1052b00a9d7f 
+0

これが速ければ、それはトリックを行います。しかし、私はあなたの例で引っ張った線以外のXMLから他の情報も探しています。私の仕様の特定のタグのデータをどのように引き出すことができますか? – user1897691

+0

提供された例は 'if($ self - > {path} ~~ [qw [メッセージ行を変更]]){...}'条件でタグを検出します。だから、 'author_name'を手にして、' $ self - > {path} ~~ [qw [change author_name]] 'という条件を追加してください。 – creaktive

0

ないXML ::小枝の答えが...

あなたがしている場合XMLファイルから要素を抽出するには、XSLTを検討するとよいでしょう。 xsltprocと次のXSLスタイルシートを使用して、約1分で1Gbの<change>のバグを含む変更ラインが得られました。可能な改善がたくさんありますが、私は確信しています。

<?xml version="1.0"?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" > 

    <xsl:output method="text"/> 
    <xsl:variable name="lowercase" select="'abcdefghijklmnopqrstuvwxyz'" /> 
    <xsl:variable name="uppercase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" /> 

    <xsl:template match="/"> 
    <xsl:apply-templates select="changes/change/message/line"/> 
    </xsl:template> 

    <xsl:template match="line"> 
    <xsl:variable name="lower" select="translate(.,$uppercase,$lowercase)" /> 
    <xsl:if test="contains($lower,'bug')"> 
     <xsl:value-of select="."/> 
     <xsl:text> 
</xsl:text> 
    </xsl:if> 
    </xsl:template> 
</xsl:stylesheet> 

あなたのXML処理は

  1. エキス
  2. 論争がテキスト平らにプレーンテキストのように行うことができる場合
  3. 利益

その後、XSLTは、最初にするためのツールもありそのプロセスのステップ。

関連する問題