Как я могу ускорить XML::Twig
Я использую XML::Twig
проанализировать очень большой XML-документ. Я хочу разбить его на куски на основе <change></change>
теги.
Прямо сейчас у меня есть:
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. Это идет очень медленно. Я проверил это против чтения XML из файла с $/=</change>
и написание функции, возвращающей содержимое тега 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>jbq@google.com</author_e-mail>
<author_date>Fri Apr 22 08:32:04 2011 -0700</author_date>
<commiter_name>Jean-Baptiste Queru</commiter_name>
<commiter_email>jbq@google.com</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>
5 ответов
В настоящее время ваша программа обрабатывает весь документ XML, включая данные за пределами change
элементы, которые вас не интересуют.
Если вы измените twig_handlers
параметр в вашем конструкторе twig_roots
тогда древовидные структуры будут построены только для интересующих элементов, а остальные будут игнорироваться.
my $xml = XML::Twig->new(twig_roots => { change => \&parseChange });
XML::Twig
включает в себя механизм, с помощью которого вы можете обрабатывать теги по мере их появления, а затем отбрасывать то, что вам больше не нужно, чтобы освободить память.
Вот пример, взятый из документации (которая также имеет намного больше полезной информации):
my $t= XML::Twig->new( twig_handlers =>
{ section => \§ion,
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;
}
Шахта занимает ужасно много времени.
my $twig=XML::Twig->new
(
twig_handlers =>
{
SchoolInfo => \&schoolinfo,
},
pretty_print => 'indented',
);
$twig->parsefile( 'data/SchoolInfos.2018-04-17.xml');
sub schoolinfo {
my( $twig, $l)= @_;
my $rec = {
name => $l->field('SchoolName'),
refid => $l->{'att'}->{RefId},
phone => $l->field('SchoolPhoneNumber'),
};
for my $node ( $l->findnodes( '//Street' ) ) { $rec->{street} = $node->text; }
for my $node ( $l->findnodes( '//Town' ) ) { $rec->{city} = $node->text; }
for my $node ( $l->findnodes( '//PostCode' ) ) { $rec->{postcode} = $node->text; }
for my $node ( $l->findnodes( '//Latitude' ) ) { $rec->{lat} = $node->text; }
for my $node ( $l->findnodes( '//Longitude' ) ) { $rec->{lng} = $node->text; }
}
Это случайность pretty_print? В противном случае это довольно просто.
Не ответ XML::Twig, но...
Если вы собираетесь извлекать вещи из XML-файлов, вы можете рассмотреть XSLT. Используя xsltproc и следующую таблицу стилей XSL, я получил строки изменений, содержащие ошибку, из 1 ГБ <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 может быть выполнена как
- извлечь в простой текст
- сплющить сплющенный текст
- прибыль
тогда XSLT может быть инструментом для первого шага в этом процессе.
Если ваш XML действительно большой, используйте XML::SAX. Он не должен загружать весь набор данных в память; вместо этого он последовательно загружает файл и генерирует события обратного вызова для каждого тега. Я успешно использовал XML::SAX для разбора XML размером более 1 ГБ. Вот пример обработчика 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>jbq@google.com</author_e-mail>
<author_date>Fri Apr 22 08:32:04 2011 -0700</author_date>
<commiter_name>Jean-Baptiste Queru</commiter_name>
<commiter_email>jbq@google.com</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