パスワードを忘れた? アカウント作成
12786757 journal
Perl

route127の日記: 喪女ドム(パーサ速度比較)

日記 by route127

千葉ロッテがわたモテコラボキャンペーンしてたけどちょうどその頃Mojoliciousドキュメント日本語版が存在することを知った。
Mojolicious自体を使わなくともMojo::Utilなんかは普段良く使うモジュールがまとまってて便利そうだな、と思い読んでいた。
dumperなんかはData::DumperのDumperを使ってるいるようだったのでurl_escapeもURI::Escapeをそのまま使っているのかと思ったら自前で実装していたが何か理由があるのだろうか?
http://mojolicious.org/perldoc/Mojo/Util.txt

sub url_escape {
  my ($str, $pattern) = @_;
  if   ($pattern) { $str =~ s/([$pattern])/sprintf '%%%02X', ord $1/ge }
  else            { $str =~ s/([^A-Za-z0-9\-._~])/sprintf '%%%02X', ord $1/ge }
  return $str;
}

まあでも結局DumperにしてもData::Dumper::AutoEncodeを使うようになったり、$Data::Dumper::Maxdepthの値を調節したりとかが必要になってMojo::Utilを使うようになるには至らなかった。
(あとhmac_sha1_sumの値が変わるとかの問題もあるらしい。)

ところでMojo::にはCSS3対応のHTMLパーサMojo::DOMがあるが今回はその速度比較をしていた。
比較対象はWeb::Queryのパーサ部分である。
ワンライナの時なんかはWeb::Queryのメソッドチェーンは書きやすくて楽に感じるのだが、どうも実行が遅いような気がしていた。
そこでMojo::DOMで代替できるのではないかと思いベンチマークをとることにした。

HTMLから画像のURLを抽出してリストに格納する為の(要はぶっこ抜き)コードをMojo::DOM、Web::QueryそしてHTML::TokeParserでも書き、それらの結果が同じものになることを確認してからベンチマーク用のコードを書いた。
Benchmarkモジュールを利用して書いたところ実行時にtoo few iterations for a reliable count警告出たが大体の感じをつかむのが目的だったのであまり気にしなった(まずいかも)。

use strict;
use warnings;
use Web::Query;
use Mojo::DOM;
use HTML::TokeParser;
use Data::Dumper;
use Benchmark qw(:all :hireswallclock);
 
my $html = '<html><img src="http://hogehoge.jp/fuga.jpg"></html>'; #実際はimgタグを40個程度含む400KB位のHTML
my @img;
my @img2;
my @img3;
my $results = timethese(-1, {
  'Mojo::DOM' => q/Mojo::DOM->new($html)->find('img')->map(attr => 'src')->each( sub { push(@img, $_) if defined && m<media> })/,
  'Web::Query' => q/Web::Query->new_from_html($html)->find('img')->each( sub { $_ = $_->attr('src'); push(@img2, $_) if defined && m<media> })/,
  'HTML::TokeParser' => q/my %img3;my $stream = HTML::TokeParser->new(\$html);while(my $tag = $stream->get_tag){ $_ = $tag->[1]{'src'} if $tag->[0] eq 'img';$img3{$_}++ if defined && m<media> }@img3 = keys %img3/,
}, 'none');
cmpthese($results);

結果は驚いたことにMojo::DOMが最速だった。

                   Rate       Web::Query HTML::TokeParser        Mojo::DOM
Web::Query        696/s               --             -52%             -58%
HTML::TokeParser 1448/s             108%               --             -13%
Mojo::DOM        1671/s             140%              15%               --

でもBenchmarkのドキュメントに

eval された文字列とコードリファレンスを比べると、不正確な結果となります; コードリファレンスは等価な eval された文字列よりも少し実行が遅いです。

とあったので念のために一部書き方を変えてベンチマークを取った。

my $results = timethese(-1, {
  'Mojo::DOM' => sub {
    Mojo::DOM->new($html)->find('img')->map(attr => 'src')->each( sub { push(@img, $_) if defined && m<media> });
  },
  'Web::Query' => sub {
    Web::Query->new_from_html($html)->find('img')->each( sub { $_ = $_->attr('src'); push(@img2, $_) if defined && m<media> });
  },
  'HTML::TokeParser' => sub {
    my %img3;
    my $stream = HTML::TokeParser->new(\$html);
    while(my $tag = $stream->get_tag){
      $_ = $tag->[1]{'src'} if $tag->[0] eq 'img';
      $img3{$_}++ if defined && m<media>;
    }
    @img3 = keys %img3;
  }
}, 'none');
cmpthese($results);

その結果はTokeParserが最速となったがこれは実際の感覚に近い。
Mojo::DOMがWeb::Queryに対してより速いことについては変化はなかった。

                 s/iter       Web::Query        Mojo::DOM HTML::TokeParser
Web::Query         7.42               --             -65%             -91%
Mojo::DOM          2.61             184%               --             -73%
HTML::TokeParser  0.696             967%             275%               --

とりあえず普段使う分には使い方やメソッドが似ているMojo::DOMに変えて速度向上を計るという選択は考慮に値すると判断した。
HTML::TokeParserは速いが、ワンライナでは使い辛いし、また対象のHTMLが複雑になるにつれタイプ量が増える欠点があるので結局は状況に応じてで使い分けることにはなりそう。

この議論は賞味期限が切れたので、アーカイブ化されています。 新たにコメントを付けることはできません。
typodupeerror

にわかな奴ほど語りたがる -- あるハッカー

読み込み中...