1

何百万ものウィキペディア記事のマークアップを処理する必要がある Perl スクリプトを作成しているため、速度が問題になります。

私が探しているものの 1 つは、常に次のように見えるテンプレートの出現です: {{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.080 秒で処理されます。そのままにしておくと、時間が 0m2.646s まで上がります。なんてこったい?

頭が混乱しているように聞こえるかもしれませんが、これがウィキペディアを数時間で処理するか、数週間で処理するかの違いです。

4

4 に答える 4

5

なぜ正規表現を使用しているのですか? リテラル テキスト {{ または }} の位置を探しています。Perl には、まさにそれを行うビルトインがあります: index

ウィキペディアのエントリを解析しようとしているので、ネストされたテンプレート ディレクティブを処理する必要があります。これは、たとえば、見つけた 2 番目の閉じたカーリーのセットが、2 番目の開いたカーリーのセットと必ずしも一致しないことを意味します。Perlエントリからのこのビットでは、最初の閉じのカーリーが 2 番目のオープニングのカーリーと一緒になっています。

{{Infobox プログラミング言語
| | latest_release_version = 5.10.0
| | latest_release_date = {{リリース日|mf=yes|2007|12|18}}
| | チューリング完全 = はい
}}

Perl 5.10 正規表現はバランスの取れたテキストを再帰的に照合できるため、これを処理できます。また、それを行う Perl モジュールもあります。ただし、それは少し手間がかかります。達成しようとしていることを口にしない限り、アドバイスを与えることは困難です。確かに、あなたがやろうとしていることを実行できる mediawiki パーサーがそこにあります。


ソリューションをコーディングするつもり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";
    }

私の 17 インチ MacBook Pro での私の perl:

macbookpro_brian[349]$ perl -V
私の perl5 (リビジョン 5 バージョン 8 サブバージョン 8) 構成の概要:
  プラットホーム:
    osname=ダーウィン、osvers=8.8.2、archname=ダーウィン-2レベル
    uname='darwin macbookpro.local 8.8.2 ダーウィン カーネル バージョン 8.8.2: 2006 年 9 月 28 日 20:43:26 pdt; root:xnu-792.14.14.obj~1release_i386 i386 i386 '
    config_args='-des'
    ヒント = 推奨、useposix = true、d_sigaction = 定義
    usethreads=undef use5005threads=undef useithreads=undef usemultiplicity=undef
    useperlio=define d_sfio=undef uselargefiles=usesocks=undef を定義
    use64bitint=未定義 use64bitall=未定義 uselongdouble=未定義
    usemymalloc=n、bincompat5005=undef
  コンパイラ:
    cc='cc', ccflags ='-fno-common -DPERL_DARWIN -no-cpp-precomp -fno-strict-aliasing -pipe -Wdeclaration-after-statement -I/usr/local/include -I/opt/local/含む'、
    最適化='-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/含む'
    ccversion='', gccversion='4.0.1 (Apple Computer, Inc. ビルド 5363)', gccosandvers=''
    intsize=4、longsize=4、ptrsize=4、doublesize=8、byteorder=1234
    d_longlong=定義、longlongsize=8、d_longdbl=定義、longdblsize=16
    ivtype='long'、ivsize=4、nvtype='double'、nvsize=8、Off_t='off_t'、lseeksize=8
    alignbytes=8、プロトタイプ=define
  リンカーとライブラリ:
    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=''
  動的リンク:
    dlsrc=dl_dlopen.xs、dlext=バンドル、d_dlsymun=undef、ccdlflags=''
    cccdlflags=' ', lddlflags=' -bundle -undefined dynamic_lookup -L/usr/local/lib -L/opt/local/lib'


このバイナリの特徴 (libperl から):
  コンパイル時のオプション: PERL_MALLOC_WRAP USE_LARGE_FILES USE_PERLIO
  ダーウィンの下に建てられた
  2007 年 4 月 9 日 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
于 2009-08-11T10:53:12.090 に答える
4

アップデート:

あなたのタイミングは少し疑わしいです:

#!/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 : コマンドライン : zxc.pl
TimeThis : 経過時間 : 00:00:00.985

置換$-[0]length $`完了するまでに時間がかかりすぎます (1CTRL-C分後に押しました)。

上記の単純なパターンを2_000コピーすると、タイミングは同じになります (約 0.2 秒)。したがって、$-[0]スケーラビリティのために使用することをお勧めします。

以前のディスカッション

からperldoc perlvar:

# @LAST_MATCH_START
# @-

$-[0]最後に成功した一致の開始位置のオフセットです。 $-[n]n 番目のサブパターンに一致する部分文字列の開始位置のオフセット、またはサブパターンが一致しなかった場合は undef です。

も参照してください@+

パターンにno がないため、正規表現の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.
于 2009-08-11T02:19:55.577 に答える
3

$+[0]単なる配列ルックアップではありません。マジック インターフェイスを使用して正規表現の結果構造を調べ、目的の値を検索します。しかし、その 2000 回の反復に 2 秒かかるとは信じられません。実際のベンチマークを投稿できますか?

Sinan Ünür の提案に従って、pos を使用してみましたか?

更新: バイト オフセットと文字オフセット (効率的にキャッシュする必要があります) の間の変換がパフォーマンスを低下させている可能性があります。最初に文字列に対して utf8::encode() を実行してから、必要に応じてキャプチャされたテキストの個々の部分に対して utf8::decode を実行してみてください。

于 2009-08-11T03:46:20.290 に答える
0

ウィキペディア サーバーで実行している場合を除き、ネットワーク レイテンシはスクリプトの微調整よりも桁違いに大きくなり、その場合でもわずかです。

MediaWiki APICPAN JSON モジュールは、何をしようとしているのかにもよりますが、より役に立つかもしれません。

于 2009-08-12T12:31:12.133 に答える