Есть ли быстрый способ получить индекс персонажа для соответствия регулярному выражению? - PullRequest
1 голос
/ 11 августа 2009

Я создаю сценарий Perl, который должен будет обрабатывать разметку миллионов статей в Википедии, поэтому скорость - это проблема.

Одна из вещей, которые я ищу, - это появление шаблонов, которые всегда выглядят так: {{template}}. Поскольку они могут быть сложными и вложенными, мне нужно найти начальный и конечный теги отдельно и знать индексы символов, в которых они находятся.

Итак, вот простой код (предположим, что $ text - это текст с шаблонами в нем):

my $matchIndex ;

my $startCount = 0 ;
my $endCount = 0 ;

# find all occurrences of template start and template end tags
while($text =~ m/(\{\{)|(\}\})/gs) {

    $matchIndex = $+[0] ;

    if (defined $1) {
        #this is the start of a template
        $startCount ++ ;
    } else {
        #this is the end of a template
        $endCount++ ;
    }
 }

Действительно странная вещь в этом коде состоит в том, что строка $matchIndex = $+[0] ; имеет огромное значение для эффективности, даже если она просто ищет значение в массиве. Без этого, сложная статья в Википедии (содержащая 2000 шаблонов - безумно, но это случается) обрабатывается в 0m0.080s. Сохранение этого значения увеличивает время до 0,6646 с. Что за черт?

Возможно, это звучит так, будто я расщепляю волосы, но в этом разница между обработкой Википедии в часах или обработкой в ​​неделях.

Ответы [ 4 ]

5 голосов
/ 11 августа 2009

Почему вы используете регулярное выражение? Вы ищете позицию буквального текста {{или}}. В Perl есть встроенная функция, которая делает именно это: index .

Поскольку вы пытаетесь проанализировать записи в Википедии, вам нужно обрабатывать вложенные директивы шаблона. Это означает, что, например, второй набор найденных вами закрывающих фигур не обязательно соответствует второму набору открытых фигур. В этом бите из записи Perl первое закрывающее фигурное число идет вместе со вторым открывающим:

{{Infobox programming language
| latest_release_version = 5.10.0
| latest_release_date    = {{release date|mf=yes|2007|12|18}}
| turing-complete        = Yes
}}

Регулярные выражения Perl 5.10 могут справиться с этим, поскольку они могут рекурсивно сопоставлять сбалансированный текст, и для этого есть модули Perl. Это будет немного работы, хотя. Трудно дать вам какой-либо совет, пока вы не скажете, чего пытаетесь достичь. Наверняка есть анализатор медиавики, который может делать то, что вы пытаетесь сделать.


Я собирался закодировать свое index() решение, но я этого не сделал. Я не могу заставить ваш код быть настолько медленным, чтобы это имело значение. И решения pos(), и @- выполняются для меня практически мгновенно, даже когда я делаю все управление стеками и печатаю содержимое каждого шаблона. Мне пришлось очень постараться, чтобы он работал достаточно медленно, чтобы его можно было измерить, и я использую какое-то старое оборудование. Возможно, вам придется настроить приложение другим способом.

Вы уверены, что код, который вы измеряете, замедляется в точке, о которой вы думаете? Вы профилировали его с помощью Devel :: NYTProf , чтобы увидеть, что делает ваша настоящая программа?

#!/usr/bin/perl
use strict;
use warnings;

use Benchmark;

my $text = do { local $/; <DATA> }; # put the contents after __END__

my %subs = (
    using_pos     => sub {
        my $page = shift;

        my @stack;
        my $found;
        while( $$page =~ m/ ( \{\{ | }} ) /xg ) {           
            if( $1 eq '{{' ) { push @stack, pos($$page) - 2; }
            else             
                { 
                my $start = pop @stack;
                print STDERR "\tFound at $start: ", substr( $$page, $start, pos($$page) - $start ), "\n";
                $found++;
                };
            }

        print " Processed $found templates => ";
        },

    using_special => sub {
        my $page = shift;

        my @stack;
        my $found;
        while( $$page =~ m/ ( \{\{ | }} ) /xg ) {           
            if( $1 eq '{{' ) { push @stack, $-[0]; }
            else             
                { 
                my $start = pop @stack;
                print STDERR "\tFound at $start: ", substr( $$page, $start, $-[0] - $start ), "\n";
                $found++;
                };
            }

        print " Processed $found templates => ";
        },

    );

foreach my $key ( keys %subs )
    {
    printf "%15s => ", $key;

    my $t = timeit( 1, sub{ $subs{$key}->( \$text ) } );
    print timestr($t), "\n";
    }

Мой Perl на моем 17 "MacBook Pro:

macbookpro_brian[349]$ perl -V
Summary of my perl5 (revision 5 version 8 subversion 8) configuration:
  Platform:
    osname=darwin, osvers=8.8.2, archname=darwin-2level
    uname='darwin macbookpro.local 8.8.2 darwin kernel version 8.8.2: thu sep 28 20:43:26 pdt 2006; root:xnu-792.14.14.obj~1release_i386 i386 i386 '
    config_args='-des'
    hint=recommended, useposix=true, d_sigaction=define
    usethreads=undef use5005threads=undef useithreads=undef usemultiplicity=undef
    useperlio=define d_sfio=undef uselargefiles=define usesocks=undef
    use64bitint=undef use64bitall=undef uselongdouble=undef
    usemymalloc=n, bincompat5005=undef
  Compiler:
    cc='cc', ccflags ='-fno-common -DPERL_DARWIN -no-cpp-precomp -fno-strict-aliasing -pipe -Wdeclaration-after-statement -I/usr/local/include -I/opt/local/include',
    optimize='-O3',
    cppflags='-no-cpp-precomp -fno-common -DPERL_DARWIN -no-cpp-precomp -fno-strict-aliasing -pipe -Wdeclaration-after-statement -I/usr/local/include -I/opt/local/include'
    ccversion='', gccversion='4.0.1 (Apple Computer, Inc. build 5363)', gccosandvers=''
    intsize=4, longsize=4, ptrsize=4, doublesize=8, byteorder=1234
    d_longlong=define, longlongsize=8, d_longdbl=define, longdblsize=16
    ivtype='long', ivsize=4, nvtype='double', nvsize=8, Off_t='off_t', lseeksize=8
    alignbytes=8, prototype=define
  Linker and Libraries:
    ld='env MACOSX_DEPLOYMENT_TARGET=10.3 cc', ldflags =' -L/usr/local/lib -L/opt/local/lib'
    libpth=/usr/local/lib /opt/local/lib /usr/lib
    libs=-ldbm -ldl -lm -lc
    perllibs=-ldl -lm -lc
    libc=/usr/lib/libc.dylib, so=dylib, useshrplib=false, libperl=libperl.a
    gnulibc_version=''
  Dynamic Linking:
    dlsrc=dl_dlopen.xs, dlext=bundle, d_dlsymun=undef, ccdlflags=' '
    cccdlflags=' ', lddlflags=' -bundle -undefined dynamic_lookup -L/usr/local/lib -L/opt/local/lib'


Characteristics of this binary (from libperl): 
  Compile-time options: PERL_MALLOC_WRAP USE_LARGE_FILES USE_PERLIO
  Built under darwin
  Compiled at Apr  9 2007 10:36:26
  @INC:
    /usr/local/lib/perl5/5.8.8/darwin-2level
    /usr/local/lib/perl5/5.8.8
    /usr/local/lib/perl5/site_perl/5.8.8/darwin-2level
    /usr/local/lib/perl5/site_perl/5.8.8
    /usr/local/lib/perl5/site_perl
4 голосов
/ 11 августа 2009

Обновление:

Ваше время немного подозрительно:

#!/usr/bin/perl

use strict;
use warnings;

my $text = '{{abcdefg}}' x 100_000;

my @match_pos;
my ($start_count, $end_count);

while ( $text =~ /({{)|(}})/g ) {
    push @match_pos, $-[0];
    if ( defined $1 ) {
        ++$start_count;
    }
    else {
        ++$end_count;
    }
}

Давай пора это:

C:\Temp> timethis zxc.pl

TimeThis :  Command Line :  zxc.pl
TimeThis :  Elapsed Time :  00:00:00.985

Замена $-[0] на length $` занимает слишком много времени для завершения (я нажал CTRL-C через минуту).

Если я сделаю 2_000 копий простого шаблона, приведенного выше, время будет одинаковым (около 0,2 секунды). Поэтому я бы порекомендовал использовать $-[0] для масштабируемости.

Предыдущее обсуждение

С perldoc perlvar:

# @LAST_MATCH_START
# @-

$-[0] - смещение начала последнего успешного матча. $-[n] - смещение начала подстроки, совпадающее с n-й subpattern или undef, если subpattern не совпадает.

См. Также @+.

Опция s в вашем регулярном выражении не нужна, поскольку в шаблоне нет ..

Вы смотрели на Text::Balanced?

Вы также можете использовать pos, хотя я не уверен, что он удовлетворит ваши требования к производительности.

#!/usr/bin/perl

use strict;
use warnings;

use File::Slurp;

my $text = read_file \*DATA;

my @match_pos;
my ($start_count, $end_count);

while ( $text =~ /({{)|(}})/g ) {
    push @match_pos, pos($text) - 2;
    # push @match_pos, $-[0]; # seems to be slightly faster
    if ( defined $1 ) {
        ++$start_count;
    }
    else {
        ++$end_count;
    }
}

for my $i ( @match_pos ) {
    print substr($text, $i, 2), "\n";
}

__DATA__
Copy & paste the source of the complicated Wikipedia page here to test.
3 голосов
/ 11 августа 2009

$+[0] - это , а не , это просто поиск в массиве; он использует магический интерфейс, чтобы углубиться в структуру результатов регулярных выражений, чтобы найти нужное значение. Но мне трудно поверить, что 2000 итераций этого занимают 2 секунды. Можете ли вы опубликовать фактический тест?

Вы пробовали использовать pos, как это предложил Синан Юнюр?

Обновление: мне приходит в голову, что перевод между байтовым смещением и символьным смещением (который должен эффективно кэшироваться) может ухудшить вашу производительность. Попробуйте запустить utf8 :: encode () сначала в вашей строке, а затем при необходимости utf8 :: decode для отдельных фрагментов захваченного текста.

0 голосов
/ 12 августа 2009

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

MediaWiki API и CPAN JSON-модуль может оказаться более полезным для вас, в зависимости от того, что вы, конечно, пытаетесь сделать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...