Home > Perl > XML::Simple は遅い説における意外な落とし穴

XML::Simple は遅い説における意外な落とし穴

  • 2005-11-29 (火)
  • Perl
  • このエントリーを含むはてなブックマーク

「XML::Simpleは処理速度が遅い」説といえばNDO::Weblogさんの記事 Perl で XML の処理はどれが速いかベンチ がすぐに思い浮かびます。初見した時はずいぶん感心した覚えがあり、以来、業務で XML::Simple を使う事に多少なりとも躊躇するようになったものです。そんな中、現在仕事で XML parsing with Perl な案件を受け、CPANにUPされている様々な XML 解析系モジュールの処理速度を計測しているのですが、その際気付いた、XML::Simple の意外な落とし穴についてまとめてみました。

目次:

  1. まとめ
  2. デフォルトで呼ばれるXML::SAXパーサーの確認方法
  3. ベンチマーク比較
  4. 関連情報

まとめ

だらだら説明するのもナンなので、いきなり結論から。XML::Simple は、デフォルトの状態だと

XML::SAX がインストールされている場合、明示的な指定が無い限り、XML::Parser よりも XML::SAX 側のパーサーを優先的に利用する

らしいです。てっきりパーサー指定をまったくしない場合は XML::Parser を呼び出してくれているんだろうと勝手に認識しちゃっていましたが、実はその逆でした。この事は perldoc の ENVIRONMENT セクションにちゃんと詳しく書かれていて、簡単に(テキトウに)まとめると、

  • 環境変数「XML_SIMPLE_PREFERRED_PARSER」またはパッケージ変数「$XML::Simple::PREFERRED_PARSER」にて利用したいパーサーを指定できるよ
  • これら設定が undef の場合、XML::SAX を優先的に使うよ
  • これら設定は初期状態では undef だよ

てな具合です。さらに、これら設定が undef の場合、XML::SAX が内部的に利用するパーサーはユーザが最後にインストールしたSAXパーサーになるそうで。つまり、XML::LibXMLXML::SAX::Expat 等のオプショナルなSAXパーサーがどれもインストールされていない環境では、

PurePerlな、でも決して高速では無い XML::SAX::PurePerl パーサーが使われる

場合もあるって事。詳細は後述しますが、$XML::Simple::PREFERRED_PARSER = 'XML::Parser' および 'XML::SAX::PurePerl' それぞれでこんな感じのXML(75KB)を500回 parse してみたベンチマークコードの実行結果は

$ ./bench1.pl sampleLarge.xml
Benchmark: timing 500 iterations of SimpleSAX, SimpleXP...
SimpleSAX: 1068 wallclock secs (202.55 usr + 865.64 sys = 1068.19 CPU)
 SimpleXP:   40 wallclock secs ( 39.47 usr +   0.47 sys =   39.94 CPU)

と、その差は顕著でした。何気なくベンチマークとってみたら極端に処理速度が遅かった・・・そんな時は裏で XML::SAX::PurePerl パーサーが選択されてしまっている可能性を疑ってみてください。

以上のように、単に「XML::Simpleの処理速度」と言っても、利用するパーサーによって速度はまったく異なります。どれを使っとけば無難かと言えば、naoya氏の別記事 XML::Simple におけるパーサーの実行速度比較 にもあるとおり、 XML::Parser が良い感じなので、以後、XML::Simple を利用する場合は漏れなく

$XML::Simple::PREFERRED_PARSER = 'XML::Parser';

とソースコードに記述しておくのが良いかと。処理速度の劇的改善を目の当たりにして、XML::Simple が急に頼りに思えてくるかもしれませんよ。

▲RETURN TO TOP

デフォルトで呼ばれるXML::SAXパーサーの確認方法

XML::SAX の仕様的には

明示的な指定が無い限り、最後に環境にインストールされたSAXパーサーを利用する

との事です。「じゃあ俺の環境で一番最後にインストールされたパーサーは何なの?」という疑問に対しては、以下のコードを実行する事で確認できます:

use XML::SAX;
use Dumpvalue;
my $p = XML::SAX->parsers();
Dumpvalue->new->dumpValue($p);

### 以下実行結果例 ### 0 HASH(0x81dba7c)
'Features' => HASH(0x81b1864)
'http://xml.org/sax/features/namespaces' => 1
'Name' => 'XML::SAX::PurePerl'
1 HASH(0x81b1a8c)
'Features' => HASH(0x81b1840)
'http://xml.org/sax/features/namespaces' => 1
'Name' => 'XML::LibXML::SAX::Parser'
2 HASH(0x8166fa0)
'Features' => HASH(0x8166fd0)
'http://xml.org/sax/features/namespaces' => 1
'Name' => 'XML::LibXML::SAX'

XML::SAX のソースコードを読むと、一番最後にインストールされたパーサー = この配列の最後尾のパーサーになっていますので、この例の場合は XML::LibXML::SAX パーサーが使われる、という事になります。

※参照元ソースコード:
XML::SAX::ParserFactory の _parser_class 関数辺り
XML::SAX の load_parsers 関数辺り

▲RETURN TO TOP

ベンチマーク比較

ここでは XML::Simple のパーサーを変えた場合の速度比較に加えて、XML::Simple は使わずに、同様のPerl構造体変換を XML::Parser モジュールにて実施した場合との速度比較もしてみました。なので計測対象は以下の3つになっています;

  • $XML::Simple::PREFERRED_PARSER = 'XML::Parser' と明示した状態の XML::Simple
    → XML::Simple高速モード
  • $XML::Simple::PREFERRED_PARSER = 'XML::SAX::PurePerl' と明示した状態の XML::Simple
    → XML::Simple低速モード
  • XML::Parser モジュール単体で同様のPerl構造体変換を行ったもの
    → XML::Parser

※XML::Simple低速モードは利用パーサーを明示的に指定していない状態($XML::Simple::PREFERRED_PARSER = undef)で、かつ XML::LibXML::SAX 等の追加SAXパーサーをインストールしていなかった場合(最悪・最遅の場合)を想定してのモード。

ベンチマーク用のソースコードはこんな感じ:

#!/usr/bin/perl
use strict;
use warnings;
use lib './lib';
use Benchmark;
use Dumpvalue;
use encoding 'utf8';
use XML::Simple;
use XML::Parser;
die "Usage: ./bench.pl xmlfile\n" if(!@ARGV);
my $xml; { open(IN,$ARGV[0]) or die;
local $/ = undef;
$xml = <IN>;
close(IN); }
sub simpleFast {
$XML::Simple::PREFERRED_PARSER = 'XML::Parser';
my $p = XML::Simple->new();
my $r = $p->XMLin($xml,ForceArray=>1,KeyAttr=>{}) or die;
}
sub simpleSlow {
$XML::Simple::PREFERRED_PARSER = 'XML::SAX::PurePerl';
my $p = XML::Simple->new();
my $r = $p->XMLin($xml,ForceArray=>1,KeyAttr=>{}) or die;
}
sub xmlParser {
my $p = new XML::Parser(Style=>'Simple');
my $r = $p->parse($xml);
}
timethese(500,{
SimpleFast => 'simpleFast($ARGV[0])',
SimpleSlow => 'simpleSlow($ARGV[0])',
XmlParser => 'xmlParser($ARGV[0])',
});

テスト用XMLデータやその他必要ソース一式を含めたアーカイブもここに置いておきます

で、まずは小さめなサイズ(2KB)の、日本語が含まれた UTF-8 なXMLデータで速度計測してみたところ、結果は以下のとおりでした:

$ ./bench.pl sampleSmall.xml
Benchmark: timing 500 iterations of SimpleSlow, SimpleFast, XmlParser...
SimpleSlow: 32 wallclock secs ( 1.66 usr + 30.45 sys = 32.11 CPU)
SimpleFast: 2 wallclock secs ( 0.82 usr + 0.76 sys = 1.58 CPU)
XmlParser: 1 wallclock secs ( 0.93 usr + 0.16 sys = 1.09 CPU)

すでに XML::Simple 低速モード (=XML::SAX::PurePerlパーサー) の遅さが顕著に現れていますね。では続いて、もうちょっと大きめなサイズ(75KB)のXMLデータで計測してみると:

$ ./bench.pl sampleLarge.xml
Benchmark: timing 500 iterations of SimpleSlow, SimpleFast, XmlParser...
SimpleSlow: 1068 wallclock secs (202.55 usr + 865.64 sys = 1068.19 CPU)
SimpleFast: 40 wallclock secs ( 39.47 usr + 0.47 sys = 39.94 CPU)
XmlParser: 20 wallclock secs ( 0.36 usr + 20.39 sys = 20.75 CPU)

てな感じになりました。

以上、ここまでの結果から、

XML::SAX::PurePerl 利用の XML::Simple だと速度は激遅になるけれど、パーサー指定を 'XML::Parser' と明示した際の XML::Simple ならばそれなりに高速動作する

事が推測出来るのではないかと思われます。

▲RETURN TO TOP

関連情報

おまけ編 - 背後で利用している XML::SAXパーサーの確実な確認方法
おまけ編 - $/ 変数の状態によって利用パーサーが変わる妙について

▲RETURN TO TOP

Comments:2

ゆうすけ 2006-12-05 (火) 09:23

これは知らなかった!

bashi 2006-12-11 (月) 15:12

perldocの一番冒頭に書いておいて欲しいですよね、この隠れ仕様。

Comment Form

コメントを表示する前にこのブログのオーナーの承認が必要になることがあります。

Remember personal info

Home > Perl > XML::Simple は遅い説における意外な落とし穴

Search
Feeds

Page Top