3 キャリア対応の HTML::MobileJpCSS 0.02 をチューニング
3 キャリアの CSS の実装の差異を吸収してインライン展開してくれるという、HTML::MobileJpCSS ですが、CSS ファイルが大きくなってくるにつれ、処理時間が気になってきたので、内部処理を見直してチューニングしてみました。
チューニング作業は Devel::NYTProf を使いながら行ったのですが、Devel::NYTProf の使い方はまた後日紹介します。
主に行った修正は、ループ処理の見直しと、正規表現の見直しです。複数回呼び出されてるパターンマッチに o を付けたり、大きめの引数は参照渡しにしたりなど、微々たる修正がほとんどですが、チリも積もれば山となるで、結果12倍近くパフォーマンスアップしました。
以下の修正箇所の diff で、問題が無ければ、CodeRepos の方にコミットしたいと思いますので、id:komoriya さんご意見いただければと思います。
ベンチマーク結果
Benchmark: timing 10000 iterations of 0.02, after... 0.02: 354 wallclock secs (327.05 usr + 27.54 sys = 354.59 CPU) @ 28.20/s (n=10000) after: 27 wallclock secs ( 0.06 usr + 26.61 sys = 26.67 CPU) @ 374.95/s (n=10000) Rate 0.02 after 0.02 28.2/s -- -92% after 375/s 1230% --
TODO
現状では、置換を行う apply() で CSS::Tiny よる CSS のパースも行っていますので、WAF から使う場合、リクエストの度にパースすることになります。ですので、CSS のパース自体は apply() とは別の場所で行い、結果をインスタンスに持つなどして、CSS ファイルに更新があった時だけ再びパースできるように、リロード用のメソッドを追加すると良いかなと思ってます。
CSS::Tiny->read() が結構な負担になっているので、上記の変更で更に倍くらいの高速化が望めると思います。
修正箇所
--- lib/HTML/MobileJpCSS.pm.orig 2009-03-01 02:18:11.000000000 +0900 +++ lib/HTML/MobileJpCSS.pm 2009-03-01 04:05:33.000000000 +0900 @@ -57,17 +59,18 @@ return $content if $self->{agent}->is_non_mobile; return $content if $self->{agent}->is_ezweb && !(map { $self->{$_} } qw/inliner_ezweb css_file css/); - $content =~ s/(?:\r\n|\n)/\n/g; + $content =~ s/\x0D\x0A/\n/g; + $content =~ tr/\x0D/\n/; my @css; - my @link = $content =~ /<link\s.*?rel="stylesheet".*?>/isg; + my @link = $content =~ /<link\s.*?rel="stylesheet".*?>/isgo; for (@link) { - if (/href="(.+?)"/) { + if (/href="(.+?)"/o) { my $css = $self->_read_href($1); push @css, $css; } } - $content =~ s/<link\s.*?rel="stylesheet".*?>\s*//isg if @link; + $content =~ s/<link\s.*?rel="stylesheet".*?>\s*//isgo if @link; push @css, $self->_read($self->{css_file}) if $self->{css_file}; push @css, bless $self->{css}, 'CSS::Tiny' if $self->{css}; @@ -75,17 +78,17 @@ my $style = {}; for my $css (@css) { for (keys %$css) { - if (/^a:(?:link|focus|visited)$/) { + if (/^a:(?:link|focus|visited)$/o) { $style->{pseudo}->{$_} = $style->{pseudo}->{$_} ? { %{$style->{pseudo}->{$_}}, %{$css->{$_}} } : $css->{$_}; } - elsif (/^(\#(.+))/) { + elsif (/^(\#(.+))/o) { $style->{id}->{$2} = $style->{id}->{$2} ? { %{$style->{id}->{$2}}, %{$css->{$1}} } : $css->{$1}; } - elsif (/^(\*?\.(.+))/) { + elsif (/^(\*?\.(.+))/o) { $style->{class}->{$2} = $style->{class}->{$2} ? { %{$style->{class}->{$2}}, %{$css->{$1}} } : $css->{$1}; @@ -105,38 +108,35 @@ $content =~ s{<head>(.*)</head>}{<head>$1<style type="text/css">\n$pseudo</style></head>}is; } - # tag - for my $tag (keys %{$style->{tag}}) { - my $props = $style->{tag}->{$tag}; - my @node = $content =~ /<$tag[^<>]*?>/isg; - for my $node (@node) { - $content = $self->_replace_style($content, $node, $props); - } - } + $self->{carrier} = $self->{agent}->carrier; + $self->{carrier} =~ tr/VH/SW/; - # id - for my $id (keys %{$style->{id}}) { - my $props = $style->{id}->{$id}; - my @node = $content =~ /<[^<>]+?id="$id"[^<>]*?>/isg; - for my $node (@node) { - $content = $self->_replace_style($content, $node, $props); + my %nodes = $content =~ /(<(\w+)[^>]*>)/sgo; + while (my ($node, $tag) = each %nodes) { + my %props; + if (my $props = $style->{tag}{$tag}) { + %props = %$props; } - } - - # class - for my $class (keys %{$style->{class}}) { - my $props = $style->{class}->{$class}; - my @node = $content =~ /<[^<>]+?\sclass="$class"[^<>]*?>/isg; - for my $node (@node) { - $content = $self->_replace_style($content, $node, $props); + if (my ($id) = $node =~ /id\s*=\s*["']([^"']+)/io) { + if (my $props = $style->{id}{$id}) { + %props = (%props, %$props); + } + } + if (my ($class) = $node =~ /class\s*=\s*["']([^"']+)/io) { + if (my $props = $style->{class}{$class}) { + %props = (%props, %$props); + } + } + if (%props) { + $self->_replace_style(\$content, $node, $tag, \%props); } - $content =~ s/<([^<>]+?)\sclass="$class"([^<>]*?)>/<$1$2>/isg; } + $content =~ s/\s*class\s*=\s*["'][^"']*["']//ig; # istyle for DoCoMo if ($self->{agent}->is_docomo) { - $content =~ s/(<input[^>]*?)(istyle="(\d)")([^>]*?>)/$1style="$IstyleDoCoMo{$3}"$4/isg; - $content =~ s/(<textarea[^>]*?)(istyle="(\d)")([^>]*?>)/$1style="$IstyleDoCoMo{$3}"$4/isg; + $content =~ s/(<input[^>]*?)(istyle="(\d)")([^>]*?>)/$1style="$IstyleDoCoMo{$3}"$4/isgo; + $content =~ s/(<textarea[^>]*?)(istyle="(\d)")([^>]*?>)/$1style="$IstyleDoCoMo{$3}"$4/isgo; } return $content; } @@ -154,8 +154,8 @@ my %CSS; sub _read_href { my ($self, $href) = @_; - if ($href !~ m{^https?://}) { - $href =~ s/\?.+$//; + if ($href !~ m{^https?://}o) { + $href =~ s/\?.+$//o; $href = File::Spec->catdir($self->{base_dir}, $href); return $self->_read($href); } @@ -196,22 +196,20 @@ } sub _replace_style { - my ($self, $content, $node, $props) = @_; - my ($tag) = $node =~ /^<([^\s]+).*?>/is; + my ($self, $content, $node, $tag, $props) = @_; my $replace = $node; my $style; for (keys %$props) { $style .= $self->_filter($tag, $_, $props->{$_}); } - if ($node =~ /style=".+?"/) { + if ($node =~ /style=".+?"/o) { $replace =~ s/(style=".+?)"/$1$style"/is; } else { $replace =~ s/<$tag/<$tag style="$style"/is; } - $replace =~ s/[\n\s]+/ /g; - $content =~ s/$node/$replace/is; - return $content; + $replace =~ s/[\n\s]+/ /go; + $$content =~ s/\Q$node\E/$replace/igs; } sub _filter { @@ -219,15 +217,12 @@ return "$property:$value;" unless $StyleMap->{$tagname}; return "$property:$value;" unless $StyleMap->{$tagname}->{$property}; my $style = $StyleMap->{$tagname}->{$property}; - my $carrier = $self->{agent}->carrier; - $carrier =~ s/^V$/S/; - $carrier =~ s/^H$/W/; if (ref $style eq 'ARRAY') { - my $prop = $style->[0]->{$value}->{$carrier}; + my $prop = $style->[0]->{$value}->{$self->{carrier}}; return "$prop;" if $prop; } elsif ($style) { - my $prop = $style->{$carrier}; + my $prop = $style->{$self->{carrier}}; return "$prop:$value;" if $prop; } return "$property:$value;";
追記 [01:25]
先ほど公開したパッチにはバグがありましたので、修正版が完成しだい UP しなおします。
追記 [04:12]
修正版を UP しました。