XML::Parser(expat)使ってみたけれど

XML パースのベンチマークとして有名なローサベンチ

ベンチの内容は RSSpermalink を取り出すだけのものですが,
XML::LibXML や XML::Simple は一度 XML 木を生成しているのでちょっと無駄っぽい。XML::Parser のストリームタイプ(コールバックスタイル)で扱えば,がんばれば勝てるのではないかと思ってごりごり書いてみました。

15アイテムの RSS ファイルを元にやってみましたが…

Benchmark: timing 100 iterations of XML::LibXML, XML::Parser, XML::RSS, XML::Simple, regexp...
XML::LibXML:  1 wallclock secs ( 0.85 usr +  0.01 sys =  0.86 CPU) @ 116.28/s (n=100)
XML::Parser:  3 wallclock secs ( 2.84 usr +  0.01 sys =  2.85 CPU) @ 35.09/s (n=100)
   XML::RSS: 68 wallclock secs (67.63 usr +  0.01 sys = 67.64 CPU) @  1.48/s (n=100)
XML::Simple: 14 wallclock secs (14.18 usr +  0.15 sys = 14.33 CPU) @  6.98/s (n=100)
     regexp:  0 wallclock secs ( 0.04 usr +  0.00 sys =  0.04 CPU) @ 2500.00/s (n=100)
            (warning: too few iterations for a reliable count)

うーん,XML::LibXML には勝てなかったです。なぜか XML::RSS律速になってるし。ちなみにバージョンは,

です。

ソースはこちら。
簡易ステートマシンを構築する必要があるので,XML::Parser の版だけモジュールにわけました。

package RetrievePermalink;

use strict;
use XML::Parser;

our $ONLY_COUNT_LINES = 0;

my $STATE_START = 0;
my $STATE_ITEM  = 1;
my $STATE_LINK  = 2;

my $state;
my @permalinks;
my $num_permalinks;
my @strings;

sub parse {
    _get_parser()->parse(@_);

    return $ONLY_COUNT_LINES ? $num_permalinks
                             : @permalinks     ;
}

sub parsefile {
    _get_parser()->parsefile(@_);

    return $ONLY_COUNT_LINES ? $num_permalinks
                             : @permalinks     ;
}

sub _get_parser {
    @permalinks = ();
    $num_permalinks = 0;
    $state = $STATE_START;

    return XML::Parser->new(
        Handlers => {
            Start => \&_elem_start,
            End   => \&_elem_end,
            Char  => \&_char,
        },
    );
}

sub _elem_start {
    my ($expat, $element) = @_;

    return unless $element eq 'item'
               || $element eq 'link';

    if ($element eq 'item') {
        return unless $state == $STATE_START;
        $state = $STATE_ITEM;
    }
    elsif ($element eq 'link') {
        return unless $state == $STATE_ITEM;
        $state = $STATE_LINK;
        @strings = ();
    }
}

sub _elem_end {
    my ($expat, $element) = @_;

    return unless $element eq 'item'
               || $element eq 'link';

    if ($element eq 'item') {
        return unless $state == $STATE_ITEM;
        $state = $STATE_START;
        if (@strings) {
            $num_permalinks ++;
            if (! $ONLY_COUNT_LINES) {
                push @permalinks, join('', @strings);
            }
            @strings = ();
        }
    }
    elsif ($element eq 'link') {
        return unless $state == $STATE_LINK;
        $state = $STATE_ITEM;
    }
}

sub _char {
    my ($expat, $string) = @_;
    return unless $state == $STATE_LINK;
    push @strings, $string;
}

1;

すんごい巨大な XML ファイルを食わせればメモリ効率の点でも XML::Simple 等に勝てると思いますが調べていません。