2015-02-17 1 views
2

Ниже приводится код, который я написал для того, чтобы фильтровать мой 3 до 5 Гб XML-файл на основе четырех условий:Performance :: Twig в Perl

Ниже приведены условия:

1) Все подпапки должны быть отфильтрованы.

2) Запасы, имеющие определенное происхождение, должны сохраняться. Остальное должно быть отфильтровано.

3) Внутри торговли акциями есть тег <event>, который впоследствии имеет тег <subevent>. Для атрибута «Код» атрибута «<event>» должно быть значение «abc», а тег <subevent> должен иметь определенные значения для своих атрибутов, как видно из кода.

4) Должна быть сохранена только самая высокая версия (атрибут запаса) для данного ref (другой атрибут запаса). Остальные все должны быть удалены (это один является наиболее сложным условием)

Мой код:

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». Кто-нибудь может помочь с некоторыми советами по оптимизации.

ответ

3

Если вы беспокоитесь об объеме памяти, вы действительно хотите использовать флеш/чистку в обработчике веточек. Таким образом, он вызывается, когда файл разбирается.

Ваши звонки на очистку производятся после ваш синтаксический анализ, что означает - он должен сначала загрузить и разобрать все.

Возможно, вы сможете построить часть этого в своем trade_handler - например. вы проверяете максимальную версию, а затем сравниваете ее в своем итерационном цикле позже. Таким образом, вы могли бы, вероятно, тест, состояние во время обработчика:

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

Но имейте в виду, если вы сделали это, вы должны пересмотреть свой пост-синтаксического анализа foreach цикла, потому что вы бы отбрасывая, как вы отправился.

Я не совсем уверен, что ваш grep для этого. Возможно, вы тоже можете реализовать эту логику в вашем trade_handler, но я не могу сказать точно. (например, отрицательный тест, продувка, если этот элемент не требуется).

Я думаю - довольно принципиально - вы должны иметь возможность использовать обработчик для цикла «for», а также обрабатывать и очищать, когда идете. Я не могу сказать точно - вам может понадобиться двухпроходный подход, потому что вам нужно искать номера версий.

Edit: Это не совсем то, что вы хотите, но, надеюсь, иллюстрирует то, что я бы предположить:

#!/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, как вы идете, так что у вас есть только в памяти один <STOCK> в данный момент времени. Тогда у вас есть отношения между «ref» и «самой высокой версией».

Итак, вы разыгрываете второй раз, и, поскольку вы знаете, что такое самая высокая версия, вы можете очищать/стирать, когда вы идете - вам не обязательно читать будущее.

Теперь, как отмечено в комментариях, ваш первый подход читает весь файл в памяти, потому что ему нужно знать «самую высокую версию». Тем не менее, это единственный проход, который делает его быстрее.

Другой подход - два прохода - чтение файла дважды. Медленно, но не сохраняя очень много в памяти.

полпути дом может быть:

#!/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, поскольку корневой узел <STOCKEXT> будет отброшен Полагая, что в начале $out и </STOCKEXT> в конце будет решать, что хотя).

+0

Не могли бы вы набросать код ... Я имею в виду переписать раздел моего кода ... таким образом, чтобы реализовать вашу идею. Я немного озадачен. –

+0

Не легко. Это существенное изменение в том, как работает ваша работа - вы повторяете анализ XML-сообщений. Чтобы очистить, вам нужно разобрать и очистить, как вы идете. – Sobrique

+0

нет ... не всегда ... последовательность не уверена ... Но нужно сохранить только те запасы, которые имеют наибольшее значение для атрибута 'version' для тех же значений ref ... В случае, если два имеют одинаковый максимум значение атрибута версии для данного ref .... тогда оба должны быть сохранены. Оба они являются законными сделками .... –