tokuhirom@株主の日記 このページをアンテナに追加 RSSフィード

 | 

2010-05-07

Plack::Request#uri をキャッシュしつつ、ちゃんと expire もできる実装について考える 01:52 Plack::Request#uri をキャッシュしつつ、ちゃんと expire もできる実装について考える - tokuhirom@株主の日記 を含むブックマーク

Plack::Request の現在の実装では、uribase を一切キャッシュしておらず、これらのメソッドを多様すると、URI オブジェクトの生成コストが支配的となってしまうことがある(実際に、私のプロジェクトでこの問題が発生した)。

そこで、Variable::Magic を利用し、データの変更時にキャッシュを clear するようにしつつ、キャッシュをおこなうようにしてみた。

さて、いかがだろうか。なお、ベンチ結果はいかのとおり。

          Rate  orig  fast
orig   15582/s    --  -99%
fast 1048176/s 6627%    --
package Plack::Request::Cached;
use strict;
use warnings;
use parent qw/Plack::Request/;
use Variable::Magic qw/cast wizard VMG_OP_INFO_NAME/;
use Scalar::Util qw/refaddr weaken/;

our %MAP;

my $wiz = wizard(
    store => sub {
        delete $MAP{ refaddr( $_[0] ) }->{uri};
        delete $MAP{ refaddr( $_[0] ) }->{base};
    },
);

sub new {
    my $class = shift;
    my $self = $class->SUPER::new(@_);
    cast %{$self->env}, $wiz;
    my $addr = refaddr $self->env;
    $MAP{$addr} = $self;
    weaken($MAP{$addr});
    return $self;
}

sub DESTROY {
    my $self = shift;
    delete $MAP{refaddr $self->env};
}

sub uri {
    $_[0]->{uri} ||= do {
        my $self = shift;

        my $base = $self->_uri_base;

        my $path = $self->env->{PATH_INFO} || '';
        $path .= '?' . $self->env->{QUERY_STRING}
            if defined $self->env->{QUERY_STRING} && $self->env->{QUERY_STRING} ne '';

        $base =~ s!/$!! if $path =~ m!^/!;

        URI->new($base . $path)->canonical;
    };
}

sub base {
    $_[0]->{base} ||= do {
        my $self = shift;
        URI->new($self->_uri_base)->canonical;
    };
}

sub _uri_base {
    my $self = shift;

    my $env = $self->env;

    my $uri = ($env->{'psgi.url_scheme'} || "http") .
        "://" .
        ($env->{HTTP_HOST} || (($env->{SERVER_NAME} || "") . ":" . ($env->{SERVER_PORT} || 80))) .
        ($env->{SCRIPT_NAME} || '/');

    return $uri;
}

if ($0 eq __FILE__) {
    require Test::More;
    Test::More->import();
    {
        my $req = Plack::Request::Cached->new({HTTP_HOST => 'example.com'});
        is($req->uri, "http://example.com/");
        $req->env->{HTTP_HOST} = 'example.jp';
        is($req->uri, "http://example.jp/");
    }
    is(scalar(keys %MAP), 0);
    done_testing();

    require Benchmark;
    Benchmark->import(':all');

    my $req = Plack::Request->new({HTTP_HOST => 'example.com'});
    my $fast = Plack::Request::Cached->new({HTTP_HOST => 'example.com'});
    cmpthese(
        -1, {
            'orig' => sub {
                $req->uri;
            },
            'fast' => sub {
                $fast->uri;
            },
        },
    );
}

1;
 |