2012-12-12 32 views
5

我正在使用XML::Twig解析一个非常大的XML文档。我想根据<change></change>标签将它分成块。如何加速XML :: Twig

现在我有:

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方法时,它拉从XML该块。这是非常缓慢的。我测试了它与从$/=</change>的文件读取XML并编写一个函数来返回XML标记的内容,它的速度更快。

有什么我不知道或者我错误地使用了XML::Twig?我是Perl的新手。

编辑:这是从更改文件的示例更改。该文件包含了很多其他后这些一个正确的和不应该有它们之间的任何东西:

<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 :: Twig之前用正则表达式预处理XML是个好主意。它使你的代码不够健壮。例如,如果在评论中有'',该怎么办?另外,XML解析不太可能会减慢脚本的速度。你能提供更多的信息:文件的大小和你正在做什么样的处理? – dan1111

+0

我目前没有在任何地方使用正则表达式。一种方法是使用树枝,另一种方法是自己读取并解析它。我从整个脚本中提取了这部分内容,因此它是唯一运行的内容。 此外,文件大小为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 
    } 

具有多GB的文件时,这可能会是必不可少的,因为(再次,根据文档)将整个内容存储在内存中可能会占用文件大小的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

既然这样,你的程序处理的XML文档的所有,包括你是不感兴趣的change元素之外的数据。

如果你在改变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 [change message line]]){...}'条件来检测标签。因此,要选择一个'author_name',添加一个条件'$ self - > {path} ~~ [qw [change author_name]]'。 – creaktive

0

不是XML ::嫩枝的答案,但...

如果你要从xml文件中提取内容,您可能需要考虑XSLT。使用xsltproc和下面的XSL样式表,我在大约一分钟内得到了<change> s中1Gb以外的包含bug的更改行。我敢肯定,有很多可能的改进。

<?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可能是第一个工具步骤在那个过程。