けだし同感。

ましてや Apache Combined Log を LTSV に を書いた後では。

combined2ltsv.plの最初のバージョンのparserはこうなっていました。

sub parse_line_ng {
    my $line = shift;
    my %rec;
    ( $rec{host}, $rec{ident}, $rec{user}, $line ) = split ' ', $line, 4;
    $line =~ s/^(\[.*?\]) //;
    $rec{time} = $1;
    $line =~ s/^\"(.*?)\" //;
    $rec{req} = $1;
    ( $rec{status}, $rec{size}, $line ) = split ' ', $line, 3;
    return \%rec unless $line;    # common log
    $line =~ s/^\"(.*?)\" //;
    $rec{referer} = $1;
    $line =~ s/^\"(.*?)\"//;
    $rec{ua} = $1;
    return \%rec;
}

これでも「99個の問題が100個に」って感じですが、ソッコーで@kazuhoによってバグが指摘されます。

で、対策したのがこちら。

sub parse_line_ok {
    my $line = shift;
    my %rec;
    ( $rec{host}, $rec{ident}, $rec{user}, $line ) = split ' ', $line, 4;
    $line =~ s/^(\[.*?\]) //;
    $rec{time} = $1;
    $line =~ s/^\"((?:\\[\\\"]|.)*?)\" //;
    $rec{req} = $1;
    ( $rec{status}, $rec{size}, $line ) = split ' ', $line, 3;
    return \%rec unless $line;    # common log
    $line =~ s/^\"((?:\\[\\\"]|.)*?)\" //;
    $rec{referer} = $1;
    $line =~ s/^\"((?:\\[\\\"]|.)*?)\"//;
    $rec{ua} = $1;
    return \%rec;
}

しかしこの対策版、とても低速なのです。元の半分以下。実地での利用にあたって何百万行どころか何億行も行く事も珍しくないWebサーバーのlogを処理するにあたって、半分以下とは結構深刻です。

というわけで、少しづつrefactorを進めた結果、こうなりました。

my @common     = qw/host ident user time req status size/;
my @combined   = qw/referer ua/;
my @re_unquote = ( qr/\"(.*?)\"/, qr/\"((?:\\[\\\"]|.)*?)\"/ );
my @re_common  = map {
    qr{
    \A
    (\S+)     [ ] # host
    (\S+)     [ ] # ident
    (\S+)     [ ] # user
    (\[.*?\]) [ ] # time
    $_        [ ] # req
    (\S+)     [ ] # status
    (\S+)         # size
  }msx
} @re_unquote;
my @re_combined = map { qr/\G\s+$_ $_/ms } @re_unquote;

sub parse_line_re {
    my $line = shift;
    my %rec;
    my $escaped = !( index( $line, '\"' ) < 0 );
    @rec{@common}   = ( $line =~ m/$re_common[$escaped]/gc );
    @rec{@combined} = ( $line =~ m/$re_combined[$escaped]/ );
    return \%rec;
}

要は、必要な時にだけ遅くとも確実な正規表現を用い、それ以外の場合は単純で高速な正規表現を用いるということです。さらに双方の正規表現をあらかじめ「作り置き」しておくことで、速度も大本のバージョンをやや上割るぐらいになりました。

しかしこれだけがんばっても、コードの簡潔さにおいても速度においてもLTSVにはまるでかなわないのです。

sub parse_line_ltsv {
    +{ map { split ':', $_, 2 } split "\t", shift };
}

見ての通り、正規表現は皆無です。

'\"'を含まない場合 (iMac 27-inch, Mid 2011/3.4GHz Core i7/OS X v10.8.2/Perl 5.16.2)
        Rate   ok   ng   re ltsv
ok   28388/s   -- -60% -65% -69%
ng   71679/s 152%   -- -11% -23%
re   80389/s 183%  12%   -- -13%
ltsv 92839/s 227%  30%  15%   --
'\"'を含む場合
        Rate   ok   re   ng ltsv
ok   24976/s   -- -18% -64% -73%
re   30632/s  23%   -- -56% -67%
ng   70274/s 181% 129%   -- -24%
ltsv 92178/s 269% 201%  31%   --
LTSV のもうひとつのメリット、あるいは、プログラムでログを出力する際に気をつけるべきこと - kazuhoのメモ置き場
特に httpd のログは NCSA httpd という HTTP/0.9 時代のWebサーバのログフォーマットがベースに拡張されてきたため、以下のようにセパレータとして空白、[]、ダブルクォート ("")*1が混在するという、とても処理しづらいものになっていました。どれほど複雑かは「404 Blog Not Found:perl - Apache Combined Log を LTSV に」の実装を見れば明らかでしょう。

しかもそれが未だにデフォルトだというorz。

上のごときな正規表現は、「しかたがなく」使うものであり、そういう「必要に迫られた」段階でプログラムではなくデータ構造の方を見直すべきだというのはrule of thumbになりそうです。

LTSV FAQ - LTSV って何? どういうところが良いの? - naoyaのはてなダイアリー
できれば広く、またグローバルに広まって欲しいところです。この手の仕様は広く多くのところで使われるほど、その価値は高くなります

Apacheのhttpd.confのサンプルに搭載されればなあ…

Dan the Reluctant Regular Expressionist