webalizer用ログのデコード

今時webalizerを使っている人がどれだけいるのか知らないけど、仕事でデコードロジックを最適化する必要があったのでベンチマークを晒しておきます。
webalizerに食わせる前に、apacheログのURLをアンエスケープして、特定のエンコードに変換するというものです。

ちなみにぐぐって良く出てくるのはこれ。低レベル処理なのである程度早いかもしれないけど、どうみてもレガシー過ぎる。

        s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
        s/\\x([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
        &Jcode::convert(\$_,"euc");

ベンチマークのコード。(@Pen M 1.73GHz、testlogは1000行)

use strict;
use warnings;
use URI::Escape::XS qw/uri_unescape/;
use Encode qw//;
use Jcode;
use Benchmark qw/cmpthese/;

my $file = shift || './testlog';
open my $fh, "<", $file or die $!;

my $in_enc  = Encode::find_encoding("utf8");
my $out_enc = Encode::find_encoding("eucjp");

cmpthese(
    -1,
    {
        Legacy => \&legacy,
        Simple => \&simple,
        Fast   => \&fast,
    }
);

sub legacy {
    seek $fh, 0, 0;
    while (<$fh>) {
        s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
        s/\\x([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
        &Jcode::convert(\$_,"euc");
    }
}

sub simple {
    seek $fh, 0, 0;
    while (my $line = <$fh>) {
        $line = uri_unescape($line);
        Encode::from_to($line, 'utf8', 'eucjp');
    }
}

sub fast {
    seek $fh, 0, 0;
    my $line;
    while (<$fh>) {
        $line = uri_unescape($_);
        my @items = split " ", $line;
        $items[11] = $out_enc->encode( $in_enc->decode($items[11]) ) if $items[11] =~ /\?/;
    }
}

結果。

         Rate Simple Legacy  Split
Simple 24.5/s     --   -10%   -56%
Legacy 27.3/s    11%     --   -51%
Fast   55.7/s   127%   104%     --

倍早くなったよ。