【问题标题】:Performance tuning and optimization of XML::Twig in PerlPerl 中 XML::Twig 的性能调优和优化
【发布时间】:2015-02-17 10:57:07
【问题描述】:

以下是我为根据四个条件过滤我的 3 到 5 GB XML 文件而编写的代码:

以下是条件:

1) 应过滤所有子股票。

2) 具有特定来源的股票应该持续存在。否则应全部过滤。

3) 在股票交易中,有一个<event> 标签,随后有一个<subevent> 标签。对于<event> 标签属性,'code' 应该有值'abc',<subevent> 标签应该有特定的属性值,如代码所示。

4) 只有给定 ref(stock 的另一个属性)的最高版本(stock 的属性)才能被持久化。其余的都应该删除(这个是最复杂的条件)

我的代码是:

use strict;
use warnings;
use XML::Twig;

open( my $out, '>:utf8', 'out.xml') or die "cannot create output file  out.xml: $!";
my $twig = new XML::Twig(
    twig_roots => {  '/STOCKEXT/STOCK/STOCK'=> sub { $_->delete() },
                     '/STOCKEXT/STOCK[@origin != "ASIA"]' => sub  { $_->delete; },
                     '/STOCKEXT/STOCK' => \&trade_handler
                  },
    att_accessors => [ qw/ ref version / ],
    pretty_print  => 'indented',
);

my %max_version;
$twig->parsefile('1513.xml');
for my $stock ($twig->root->children('STOCK'))
{
   my ($ref, $version) = ($trade->ref, $trade->version);

   if ($version  eq  $max_version{$ref} &&
   grep {grep {$_->att('code') eq 'abc' and $_->att('narrative') eq 'def'}
   $_->children('subevent')} $trade->children('event[@eventtype="ghi"]'))

   {
        $trade->flush($out);
   }

   else
   {
    $trade->purge;

   }
}

sub trade_handler
{
  my ($twig, $trade) = @_;
  {
    my ($ref, $version) = ($trade->ref, $trade->version);

    unless (exists $max_version{$ref} and $max_version{$ref} >= $version)
    {
      $max_version{$ref} = $version;
    }
}

 1;
}

示例 XML

<STOCKEXT>
  <STOCK origin = "ASIA" ref="12" version="1" >(Filtered out, lower version ref)
    <event eventtype="ghi">
      <subevent code = "abc" narattive = "def" /> 
    </event>
  </STOCK>
  <STOCK origin = "ASIA" ref="12" version="2" >(highest version=2  for ref=12)
    <event eventtype="ghi">
      <subevent code = "abc" narattive = "def" /> 
    </event>
  </STOCK>
  <STOCK origin = "ASI" ref="13" version="1" >(Fileterd out "ASI" val wrong)   
    <event eventtype="ghi">
      <subevent code = "abc" narattive = "def" /> 
    </event>
  </STOCK> 

代码运行良好并提供必要的输出。但它消耗了大量的内存,即使我尝试实现“FLUSH”和“PURGE”。任何人都可以帮助一些优化技巧。

【问题讨论】:

    标签: xml perl xml-twig


    【解决方案1】:

    如果您担心内存占用,您真的想在树枝处理程序中使用刷新/清除。 这样它在文件被解析时被调用。

    您对 purge 的调用是在 您的 parsefile 之后进行的,这意味着 - 它必须首先加载并解析整个内容。

    您也许可以将其中的一部分构建到您的 trade_handler 中 - 例如你测试一个最大版本,然后在你的迭代循环中比较它。因此,您可以可能在处理程序期间测试该条件:

    if ( $max_version{$ref} > $version ) { $trade -> purge; }
    

    但请记住,如果您这样做了,您需要重新考虑您的解析后 foreach 循环,因为您会在执行过程中丢弃。

    我不完全确定你的grep 是干什么用的。您也可以在您的 trade_handler 中实现该逻辑,但我不能肯定地说。 (例如,阴性测试,如果不需要此元素,则清除)。

    我认为 - 从根本上讲 - 您应该能够为您的“for”循环使用处理程序,并在执行过程中进行处理和清除。我不能确定——你可能需要两遍方法,因为需要提前查找版本号。

    编辑:这并不完全符合您的要求,但希望能说明我的建议:

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    use XML::Twig;
    
    open( my $out, '>:utf8', 'out.xml' )
        or die "cannot create output file  out.xml: $!";
    my $twig = new XML::Twig(
        twig_roots => {
            '/STOCKEXT/STOCK/STOCK'              => sub { $_->delete() },
            '/STOCKEXT/STOCK[@origin != "ASIA"]' => sub { $_->delete; },
            '/STOCKEXT/STOCK' => \&trade_handler
        },
        att_accessors => [qw/ ref version /],
        pretty_print  => 'indented',
    );
    
    my %max_version;
    my %best_version_of;
    
    local $/;
    $twig->parse(<DATA>);
    
    foreach my $ref ( keys %best_version_of ) {
      $best_version_of{$ref} -> print;
    }
    #$twig->parsefile('1513.xml');
    
    
    sub trade_handler {
        my ( $twig, $trade ) = @_;
        my ( $ref, $version ) = ( $trade->ref, $trade->version );
    
        if ( not exists $max_version{$ref}
            or $max_version{$ref} < $version )
        {
            ###something here that replicates your grep test, as I'm not sure I've got it right. 
            $max_version{$ref}     = $version;
            $best_version_of{$ref} = $trade;
        }   
      $trade -> purge;
    }
    
    
    __DATA__
    
    <STOCKEXT>
      <STOCK origin = "ASIA" ref="12" version="1" >
        <event eventtype="ghi">
          <subevent code = "abc" narattive = "def" /> 
        </event>
      </STOCK>
      <STOCK origin = "ASIA" ref="12" version="2" >
        <event eventtype="ghi">
          <subevent code = "abc" narattive = "def" /> 
        </event>
      </STOCK>
      <STOCK origin = "ASI" ref="13" version="1" >
        <event eventtype="ghi">
          <subevent code = "abc" narattive = "def" /> 
        </event>
      </STOCK> 
    </STOCKEXT>
    

    如前所述,这并不完全符合您的要求 - 只要您能够通过清除大量 XML 来丢弃它,它就会“节省内存”...但是因为直到最后你才知道哪个版本是最高的,内存占用是不可避免的,因为在你到达那里之前,你永远不会完全知道可以安全地丢弃什么。

    因此,也许您需要一种两遍方法,首先解析以提取“最高版本号” - 就像您正在做的那样,但在进行时清除...然后在您了解它们后重新开始,因为然后你知道你可以在你去的时候清除或冲洗什么。

    您遇到此问题的原因是,在您到达文件末尾之前,您无法知道自己是否获得了最新版本。

    所以你可能需要这样做吗?

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    use XML::Twig;
    
    open( my $out, '>:utf8', 'out.xml' )
        or die "cannot create output file  out.xml: $!";
    
    my $first_pass = new XML::Twig(
        twig_roots => {
            '/STOCKEXT/STOCK/STOCK'              => sub { $_->delete() },
            '/STOCKEXT/STOCK[@origin != "ASIA"]' => sub { $_->delete; },
            '/STOCKEXT/STOCK' => \&extract_highest_version,
        },
        att_accessors => [qw/ ref version /],
        pretty_print  => 'indented',
    );
    
    my $main_parse = new XML::Twig(
        twig_roots => {
            '/STOCKEXT/STOCK/STOCK'              => sub { $_->delete() },
            '/STOCKEXT/STOCK[@origin != "ASIA"]' => sub { $_->delete; },
            '/STOCKEXT/STOCK' => \&trade_handler
        },
        att_accessors => [qw/ ref version /],
        pretty_print  => 'indented',
    );
    
    my %max_version_of;
    
    $first_pass->parsefile('1513.xml');
    $main_parse->parsefile('1513.xml');
    
    sub extract_highest_version {
        my ( $twig, $trade ) = @_;
        my ( $ref, $version ) = ( $trade->ref, $trade->version );
    
        if ( not exists $max_version_of{$ref}
            or $max_version_of{$ref} < $version )
        {
            $max_version_of{$ref} = $version;
        }
        $trade->purge;
    }
    
    sub trade_handler {
        my ( $twig, $trade ) = @_;
        my ( $ref, $version ) = ( $trade->ref, $trade->version );
        if ( $version >= $max_version_of{$ref}
            and ( $trade->first_child('event')->att('eventtype')                           eq 'ghi' )
            and ( $trade->first_child('event')->first_child('subevent')->att('code')       eq 'abc' )
            and ( $trade->first_child('event')->first_child('subevent') ->att('narattive') eq 'def' )
            )
        {
            $trade->flush;
        }
        else {
            $trade->purge;
        }
    }
    

    可能会更整洁一些,但重点是 - 你跑过一次 - 和purge ,所以在给定的时间你只有一个&lt;STOCK&gt; 在内存中。那么你就有了“ref”和“highest version”之间的关系。

    然后你再解析一次,因为你知道最高版本是什么,你可以随时清除/刷新 - 你不必阅读未来。

    现在正如 cmets 中所述 - 您的第一种方法将整个文件读入内存,因为它需要知道“最高版本”。虽然它是单次通过,但速度更快。

    另一种方法是两次通过 - 两次读取文件。速度较慢,但​​根本不会保留太多内存。

    一个中途的房子可能是:

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    use XML::Twig;
    
    open( my $out, '>:utf8', 'out.xml' )
        or die "cannot create output file  out.xml: $!";
    my $twig = new XML::Twig(
        twig_roots => {
            '/STOCKEXT/STOCK/STOCK'              => sub { $_->delete() },
            '/STOCKEXT/STOCK[@origin != "ASIA"]' => sub { $_->delete; },
            '/STOCKEXT/STOCK' => \&trade_handler
        },
        att_accessors => [qw/ ref version /],
        pretty_print  => 'indented',
    );
    
    my %max_version_of;
    my %best_version_of;
    
    $twig->parsefile('1513.xml');
    
    print {$out} "<STOCKEXT>\n";
    foreach my $ref ( keys %best_version_of ) {
        foreach my $trade ( @{ $best_version_of{$ref} } ) {
            $trade->print($out);
        }
    }
    print {$out} "</STOCKEXT>\n";
    
    sub trade_handler {
        my ( $twig, $trade ) = @_;
        my ( $ref, $version ) = ( $trade->ref, $trade->version );
    
        if ((   not defined $max_version_of{$ref}
                or $version >= $max_version_of{$ref}
            )
            and ( $trade->first_child('event')->att('eventtype') eq 'ghi' )
            and
            ( $trade->first_child('event')->first_child('subevent')->att('code')
                eq 'abc' )
            and ( $trade->first_child('event')->first_child('subevent')
                ->att('narattive') eq 'def' )
            )
        {
            if ( not defined $max_version_of{$ref}
                or $version >= $max_version_of{$ref} )
            {
               #this version is higher, so anything lower is redundant - remove it
                @{ $best_version_of{$ref} } = ();
            }
            push( @{ $best_version_of{$ref} }, $trade );
            $max_version_of{$ref} = $version;
        }
        $trade->purge;
        #can omit this, it'll just print how 'big' the hash is getting. 
        print "Hash size: ". %best_version_of."\n";
    }
    

    它所做的仍然是清除,但会慢慢填满%best_version_of。虽然它可能仍然有很大的内存占用 - 它很大程度上取决于“保留”与“丢弃”的比率。

    恐怕没有最佳解决方案 - 要确定哪个版本是“最新的”,您必须运行两次文件,要么以消耗内存为代价,要么以磁盘 IO。

    (我想我必须提出一个警告——这不会产生“有效”的 XML,因为根节点 &lt;STOCKEXT&gt; 将被丢弃。将其放在 $out&lt;/STOCKEXT&gt; 的开头end 会解决这个问题)。

    【讨论】:

    • 你能写下代码吗...我的意思是重写我的代码部分...以实现您的想法的方式..我有点困惑..
    • 不容易。这是对您的工作方式的重大改变 - 您正在迭代 XML 后解析。要清除,您需要随时解析和清除。
    • 不...并非总是...序列不确定...但只有那些具有相同参考值的“版本”属性值最高的股票需要保留...在如果两个 对于给定的 ref 具有相同的最高版本属性值......那么都需要保留......因为它们都是合法交易......
    • 好的,我已经更新了一个可以如何运行的示例。不幸的是,你仍然在记忆中保留了相当多的内容。因此,实际上可能需要对其进行两次解析-第一次通过确定最高版本号,第二次通过解析和丢弃。效率不高,但可能是不可避免的,因为您希望减少内存占用。
    • @sobrique-你的想法听起来不错....但是因为我最近开始使用 perl...你能帮我写下代码吗...
    猜你喜欢
    • 2017-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-19
    • 1970-01-01
    • 2017-10-09
    • 2011-06-14
    相关资源
    最近更新 更多