あいつの日誌β

働きながら旅しています。

Perl: 動的に関数を生成してみた

あらすじ

おれおれフレームワークで Dispatch の処理を適当に書きました。こんな感じ。

my $defalut = sub {
    $controller->auto(@_) if $controller->can('auto');    
    $controller->$method(@_);
    $controller->end(@_) if $controller->can('end');
} 

これは Catalyst のインターフェースを真似しているのですが controller に auto, end のメソッドが存在する場合は dispatch された method の前後にそれを実行します。

でも処理が無駄な気がしたのでリファクタリングしてみます。

対策1: プロセス起動時に関数をスタックしておいてディスパッチテーブルと紐づける。

イメージはこんな感じ

my @functions = ( 
    sub { Controller->auto(@_) },
    sub { Controller->method(@_); },
    sub { Controller->end(@_) }
);

my $answer1 = sub {
    $_->(@_) for @functions;
};

ベンチ結果

Benchmark: timing 1000000 iterations of default, answer1...
   default:  3 wallclock secs ( 2.17 usr +  0.00 sys =  2.17 CPU) @ 460829.49/s (n=1000000)
   answer1:  2 wallclock secs ( 2.43 usr +  0.00 sys =  2.43 CPU) @ 411522.63/s (n=1000000)

ちょっと遅くなりました。どうやら配列をループする処理は思ったよりコストが高いようです。という事で別の方法を考えます。

対策2: プロセス起動時に関数を生成しておいてディスパッチテーブルと紐づける。

my $answer2 = eval('sub {' .
    'Controller->auto(@_);' .
    'Controller->method(@_);' .
    'Controller->end(@_);' .
    '};'
);

ベンチ

Benchmark: timing 1000000 iterations of answer1, answer2, default...
   answer1:  1 wallclock secs ( 2.34 usr +  0.00 sys =  2.34 CPU) @ 427350.43/s (n=1000000)
   answer2:  2 wallclock secs ( 1.37 usr +  0.00 sys =  1.37 CPU) @ 729927.01/s (n=1000000)
   default:  2 wallclock secs ( 2.02 usr +  0.00 sys =  2.02 CPU) @ 495049.50/s (n=1000000)

高速化に成功しました。

まとめ

という事で期待通りの結果になったので満足しました。以下の条件を満たす箇所がある場合はこの方法で処理が高速になるかもしれません。

  • 配列をループする箇所がある
  • その気になったらハードコーディングできる

参考URL

https://speakerdeck.com/felixge/faster-than-c-parsing-binary-data-in-javascript

ベンチコード

#!/usr/bin/env perl
use strict;
use warnings;

package Controller;

sub auto {
    my ($class, %args) = @_;
    return 1;
}

sub method {
    my ($class, %args) = @_;
    return 1;
}

sub end {
    my ($class, %args) = @_;
    return 1;
}

package main;

use Benchmark qw(:all) ;

my $count = 1_000_000;

my $controller = 'Controller';

my $default = sub {
    $controller->auto() if $controller->can('auto');     
    $controller->method();
    $controller->end() if $controller->can('end');     
};

my @functions = (
    sub { Controller->auto(@_) },
    sub { Controller->method(@_); },
    sub { Controller->end(@_) }
);

my $answer1 = sub {
    $_->(@_) for @functions;
};

my $answer2 = eval('sub {' .
    'Controller->auto(@_);' .
    'Controller->method(@_);' .
    'Controller->end(@_);' .
    '};'
);

timethese($count, {
    'default' => $default,
    'answer1' => $answer1,
    'answer2' => $answer2,
});