2014年2月アーカイブ

前のエントリで紹介した HTTP::Entity::Parser ですが、プロファイリングとるとPOSTデータの解析で半分弱の時間を使っていたのでそこをXSにしたら良いんじゃないかということで、新しく WWW::Form::UrlEncoded ってのを書いて出しました。

https://metacpan.org/release/WWW-Form-UrlEncoded
https://github.com/kazeburo/WWW-Form-UrlEncoded

似たモジュールに URL::EncodeText::QueryString がありますが、HTTP::Bodyのパーサと互換性あるこの仕様を満たしていないので、新しく書き起こしてみました。URL::Encode(::XS) がかなり速いのですが、それに近い速度がでています。

              Rate wwwform_pp    text_qs    wwwform  urlencode
wwwform_pp 11933/s         --       -77%       -87%       -88%
text_qs    52609/s       341%         --       -43%       -46%
wwwform    91995/s       671%        75%         --        -5%
urlencode  97303/s       715%        85%         6%         --

そして、これをHTTP::Entitiy::Parserに組み込んで0.03としてリリースしました

https://metacpan.org/release/HTTP-Entity-Parser
https://github.com/kazeburo/HTTP-Entity-Parser/

ベンチマークはPOSTデータのサイズを変えながら3つとってみました

## content length => 38

Benchmark: running http_body, http_entity for at least 1 CPU seconds...
 http_body:  1 wallclock secs ( 1.08 usr +  0.00 sys =  1.08 CPU) @ 36201.85/s (n=39098)
http_entity:  1 wallclock secs ( 1.12 usr +  0.00 sys =  1.12 CPU) @ 76799.11/s (n=86015)
               Rate   http_body http_entity
http_body   36202/s          --        -53%
http_entity 76799/s        112%          --

## content length => 177

Benchmark: running http_body, http_entity for at least 1 CPU seconds...
 http_body:  1 wallclock secs ( 1.11 usr +  0.00 sys =  1.11 CPU) @ 14901.80/s (n=16541)
http_entity:  1 wallclock secs ( 1.08 usr +  0.00 sys =  1.08 CPU) @ 64474.07/s (n=69632)
               Rate   http_body http_entity
http_body   14902/s          --        -77%
http_entity 64474/s        333%          --

## content length => 1997

Benchmark: running http_body, http_entity for at least 1 CPU seconds...
 http_body:  1 wallclock secs ( 1.16 usr +  0.00 sys =  1.16 CPU) @ 1930.17/s (n=2239)
http_entity:  1 wallclock secs ( 1.11 usr +  0.00 sys =  1.11 CPU) @ 29519.82/s (n=32767)
               Rate   http_body http_entity
http_body    1930/s          --        -93%
http_entity 29520/s       1429%          --

データが大きくなればなるほど差が大きくなります。

HTTP::Bodyと互換性のある HTTPのEntityをパースするモジュールをリリースしました。

https://metacpan.org/release/HTTP-Entity-Parser
https://github.com/kazeburo/HTTP-Entity-Parser/

HTTPのEntityってのは、こういう範囲を指します。

POST /foo HTTP/1.1          # Not part of the entity.
Content-Type: text/plain    # ┬ The entity is from this line down...
Content-Length: 1234        # │
                            # │
Hello, World! ...           # ┘

元ネタは「java - What exactly is an HTTP Entity? - Stack Overflow

HTTP::Entity::Parserの使い方はこんな感じ。

use HTTP::Entity::Parser;

my $parser = HTTP::Entity::Parser->new;
$parser->register('application/x-www-form-urlencoded','HTTP::Entity::Parser::UrlEncoded');
$parser->register('multipart/form-data','HTTP::Entity::Parser::MultiPart');
$parser->register('application/json','HTTP::Entity::Parser::JSON');

sub app {
    my $env = shift;
    my ( $params, $uploads) = $parser->parse($env);
}

このモジュール、元々は tokuhirom の Plackへのpullreqにあるものですが、「だれか別モジュールにして」とのことでしたので、テスト書いてHTTP::Bodyとの互換性を検証して、リリースしました。

このモジュールの開発動機はこのスライドにありますね。ただ、スライド中にでてくるURL::Encodeは互換性の問題で使ってません。

公平かどうか微妙かもですが、一応ベンチマーク。

#!/usr/bin/perl

use strict;
use warnings;
use HTTP::Entity::Parser;
use HTTP::Body;
use Benchmark qw/:all/;

my $content = 'xxx=hogehoge&yyy=aaaaaaaaaaaaaaaaaaaaa';

my $parser = HTTP::Entity::Parser->new;
$parser->register('application/x-www-form-urlencoded','HTTP::Entity::Parser::UrlEncoded');

cmpthese(timethese(-1, {
    'http_entity' => sub {
        open my $input, '<', \$content;
        my $env = {
            'psgi.input' => $input,
            'psgix.input.buffered' => 1,
            CONTENT_LENGTH => length($content),
            CONTENT_TYPE => 'application/x-www-form-urlencoded',
        };
        $parser->parse($env);
    },
    'http_body' => sub {
        open my $input, '<', \$content;
        my $body   = HTTP::Body->new( 'application/x-www-form-urlencoded', length($content) );
        $input->read( my $buffer, 8192);
        $body->add($buffer);
        $body->param;
    }
}));

__END__
Benchmark: running http_body, http_entity for at least 1 CPU seconds...
 http_body:  1 wallclock secs ( 1.08 usr +  0.00 sys =  1.08 CPU) @ 36201.85/s (n=39098)
http_entity:  1 wallclock secs ( 1.10 usr +  0.01 sys =  1.11 CPU) @ 51661.26/s (n=57344)
               Rate   http_body http_entity
http_body   36202/s          --        -30%
http_entity 51661/s         43%          --

NYTProfでプロファイルをとると、application/x-www-form-urlencodedのパースがそれなりの時間を占めるので、互換性があるXSモジュールがあるともう少し速くなるんじゃないかなーと思ってる。