XML::RSS::LibXML を使って続・Perl で XML の処理ベンチ

XML::RSS::LibXML uses XML::LibXML (libxml2) for parsing RSS instead of XML::RSS' XML::Parser (expat), while trying to keep interface compatibility with XML::RSS.

CPANXML::RSS::LibXML というモジュールが新着で上がっていました。XML::RSS は内部で expat を使う XML::Parser を XML パーザーに使っています。が、これよりも libxml2 を使う XML::LibXML の方が速くて効率が良い、ということで XML::RSS のインタフェースはそのままにパーザーを XML::LibXML に置き換えたのがこのモジュールです。(ちなみに 100% XML::RSS 互換ではないとの注意書きあり。) 作者も Daisuke Maki 氏なので安心して使えそう。

さて、随分と前にPerl で XML の処理はどれが速いかベンチなんてことを書いたのですが、XML::RSS::LibXML を使って同じベンチマークを実行してみたくなるのが Hacker ってもんです。

早速やってみました。

Benchmark: timing 1000 iterations of XML::LibXML, XML::RSS, XML::RSS::LibXML...
XML::LibXML:  2 wallclock secs ( 1.94 usr +  0.00 sys =  1.94 CPU) @ 515.46/s (n=1000)
XML::RSS: 114 wallclock secs (107.22 usr +  0.13 sys = 107.35 CPU) @  9.32/s (n=1000)
XML::RSS::LibXML: 10 wallclock secs (10.50 usr +  0.03 sys = 10.53 CPU) @ 94.97/s (n=1000)

おおー、速い! 生で XML::LibXML を使うよりも多少のオーバーヘッドがあるは当然ですが、XML::RSSXML::RSS::LibXML には 10 倍の差がありました。ちょっとベンチマークの処理が単純すぎるのですが、目安にはなりそうです。

ちなみに、ソースは以下。前回のソースに XML::RSS::LibXML 部分を足して一部コメントアウトしただけです。

#!/usr/local/bin/perl

use strict;
use warnings;
use Benchmark;
use FileHandle;
use XML::LibXML;
use XML::RSS;
use XML::RSS::LibXML;
use XML::Simple;

my $rss_file = shift or die "usage $0 <rss_file>\n";
my $fh = FileHandle->new($rss_file)
    or die "cannot open $rss_file: $!";
local $/; # slurp mode
our $content = $fh->getline;
$fh->close;

Benchmark::timethese(1000, {
#    'regexp' => \&with_regexp,
#    'XML::Simple' => \&with_xml_simple,
    'XML::RSS' => \&with_xml_rss,
    'XML::LibXML' => \&with_xml_libxml,
    'XML::RSS::LibXML' => \&with_xml_rss_libxml,
});

sub with_regexp {
    my $pattern = "<item .*?>.*?<link>(.*?)</link>.*?</item>";
    my @links =
    ($content =~ m/$pattern/smg);
}

sub with_xml_simple {
    my @links = ();
    my $parser = XML::Simple->new;
    my $data = $parser->XMLin($content, ForceArray => 1);
    for my $item (@{$data->{item}}) {
        push @links, $item->{link}->[0];
    }
}

sub with_xml_rss {
    my @links = ();
    my $rss = XML::RSS->new;
    $rss->parse($content);
    for my $item (@{$rss->{items}}) {
        push  @links, $item->{link};
    }
}

sub with_xml_libxml {
    my @links =();
    my $parser = XML::LibXML->new;
    my $doc = $parser->parse_string($content);
    my @nodes = $doc->findnodes(
    "//*[local-name()='item']/*[local-name()='link']/text()"
    );
    for my $node (@nodes) {
        push @links, $node->nodeValue;
    }
}

sub with_xml_rss_libxml {
    my @links = ();
    my $rss = XML::RSS::LibXML->new;
    $rss->parse($content);
    for my $item (@{$rss->{items}}) {
        push  @links, $item->{link};
    }
}