まだ重たいCMSをお使いですか?
毎秒 723リクエスト を捌く超高速CMS「adiary

2006/07/28(金)マニュアルから知らなかった関数

perlの関数メモ

知らなかった関数から抜粋。もっと早く読んでおけばよかったなぁ。

lc/uc

最初が小文字変換、後者が大文字変換。要するに次と等価。

$x=lc($x) : $x =~ tr/A-Z/a-z/;
$x=uc($x) : $x =~ tr/a-z/A-Z/;

study SCALAR

何回も文字列に対するパターンマッチを行なうアプリケーションで、そのような文字列 SCALAR を予め学習しておきます。(中略)別のスカラを study した場合には、以前に学習した内容は「忘却」されてしまいます。

(この study の仕組みは、まず、検索される文字列内のすべての文字のリンクされたリストが作られ、たとえば、すべての 'k' がどこにあるかがわかるようになります。各おのの検索文字列から、C プログラムや英語のテキストから作られた、頻度の統計情報に基づいて、もっともめずらしい文字が選ばれます。 この「めずらしい」文字を含む場所だけが調べられるのです。)

日本語の場合……難しいところですが、同じ(短めの)文字列に対して予めリンクリストを作っておけば、次からのパターンマッチが高速化されるようです。実際には計測して速いかどうか確認してから使えと書いてあります。

ループ関連

redo

redo コマンドは、条件を再評価しないで、ループブロックの始めからもう一度実行を開始します。 continue ブロックがあっても、実行されません

ラベル指定 last/next/redo

last などのコマンドは、ラベルによって抜けるループを指定できるようです。多段ループから抜けたいときいつも

my $z;
foreach my $x (1..9) {
  my $flag;
  foreach my $y (1..9) {
    if ($x*$y>50) { $z=$x*$y; $flag=1; last; }
  }
  if ($flag) { last; }
}

と書いてたのですが、ラベル指定を使えば

my $z;
OUT_LOOP:
foreach my $x (1..9) {
  foreach my $y (1..9) {
    if ($x*$y>50) { $z=$x*$y; last OUT_LOOP; }
  }
}

と書けばよかったようです。

正規表現/パターンマッチ

$&

$cookie_val =~ s/(\W)/ '%' . unpack('H2', $1)/eg;

とかやって URI エンコードをするのは有名ですか、わざわざ $1 のフレーズホルダーを使わなくても、

$cookie_val =~ s/\W/ '%' . unpack('H2', $&)/eg;

とすればよかったようです。$&というのはマッチした文字列そのものを示す特殊変数で、マッチ部より手前と後ろを示す $`, $' を使えば、

$cookie_val == "$`$&$'"

はマッチングが成功すれば常に成り立つことを意味してます。

:$&は正規表現の動作速度を低下させる恐れがあります。URIエンコード程度ならば$1を使いましょう(コメントでの指摘ありがとうございま)。→参考

m//;

m//; はマッチングをとる演算子で、何も付けずに//;したときと一緒。つまりは

if ($str =~ /<(\w+)>(.*?)<\/\w+>/) { print "$1 = $2\n"; }

としたときは暗黙に m が指定されているということです。/ がセパレーターなので\/エスケープしなければなりません。しかし置換表現ならば、

$str =~ s#<(\w+)>(.*?)</\w+>#print "$1=$2\n"#eg;
$str =~ s|<(\w+)>(.*?)</\w+>|print "$1=$2\n"|eg;
$str =~ s!<(\w+)>(.*?)</\w+>!print "$1=$2\n"!eg;
$str =~ s[<(\w+)>(.*?)</\w+>][print "$1=$2\n"]eg;
$str =~ s{<(\w+)>(.*?)</\w+>}{print "$1=$2\n"}eg;

などとセパレーターを変更することで、/をエスケープする必要がないわけです。でもマッチングを取るときは

if ($str =~ |<(\w+)>(.*?)<\/\w+>|) { print "$1 = $2\n"; }

とかできないで不便だなぁと思ってたのですが、つまり次みたいにすればよかったようです。

if ($str =~ m|<(\w+)>(.*?)<\/\w+>|) { print "$1 = $2\n"; }

q//;

q//; は正規表現をコンパイルした状態で保存する演算子です。正規表現をあたかもオブジェクトのように扱えます。/o オプションを使えば正規表現をコンパイルして保持できるのですが、2度とその場所の正規表現を変更できなくなるので使えませんでした。*1

@urlsを正規表現が格納された配列、@aryがマッチング処理をする配列だとします。

my $match;
LABEL: foreach my $line (@ary) {
  foreach(@urls) {
    if ($line =~ /$_/) { $match=1; last LABEL; }
  }
}

とすると、中のif文を実行する度に正規表現がコンパイルされ、@ary が大きいときに大変なロスとなります。

# 先にコンパイルしておく
foreach(@urls) { $_ = qr/$_/; }
my $match;
LABEL: foreach my $line (@ary) {
  foreach(@urls) {
    if ($line =~ /$_/) { $match=1; last LABEL; }
  }
}

とすることで、正規表現のコンパイルが1度のみとなり大きな速度向上が見込めます。ちなみにこのときのコンパイルされた正規表現式はref($str)に対して'Regexp'を返します。

my $reg = qr#<(\w+)>(.*?)</\w+>#;
print "$reg\n", ref($reg), "\n";

実行結果

(?-xism:<(\w+)>(.*?)</\w+>)
Regexp

pos SCALAR

対象の変数に対して、前回の m//g が終了した場所のオフセットを返します。また、代入することでオフセットを変えることも可能です。

と書かれています。/g 付きの正規表現は、連続マッチングを取るオプションですが、前回マッチングを取った場所より後ろが次のマッチング開始位置になります。無限ループなどにならない、至極当然の方式なのですが、極希にこの仕様が困ったことになります。

my $str = <<TEXT;
>>
1
<<
>>
2
<<

>>
3
<<
TEXT

という文字列から「>>のみの行で始まり<<のみの行で終わる」ブロックを抽出することを考えます。こののみの行ってのが厄介です。

$str = "\n$str";  # 前処理*2
$str =~ s/\n>>\n(.*?)\n<<\n/print "$1 "; "\n"/esg;

とすれば、うまく行きそうに見えます*3。ですが実際には、

1 3 

と見えて表示されません。というのも、1つめのブロックとマッチする最後の"\n"と、2つめのブロックをマッチするときの最初の"\n"が同じものを示しているためです。ここで pos 関数の出番です。

while($str =~ m|\n>>\n(.*?)\n<<\n|g) {
	print "$1(pos:", pos ($str), ")  ";
	pos($str) = length($`);
	$str = $` . "\n" . $';
}

とすれば、

1(pos:9)  2(pos:9)  3(pos:10)

となり正しく認識出来ます。

追記:もっとスマートな方法

前回マッチした部分とマッチする "\G" という要素を使えば、もっとスマートな方法で実現可能でした。

my $str = "aaa0bbb0ccc0ddd1eee0fff0ggg";
$str =~ s/(?:\G|0)(\w\w\w)0/
	print "$1\n";
	/eg;

実行結果は、

aaa [0]
bbb [4]
ccc [8]
fff [19]

また、(?:...|...)は$1などへの割り当てない部分正規表現で、マッチ部の割り当てという無駄な処理が減るので処理が効率化できます*4。また\Gを使うことで ^\w\w\w とマッチする効果があり、前処理として\nを追加する必要がなくなります。

.......ソース書きなおそ(笑

*1 : クロージャと /o オプションを組み合わせればいいかなーとか最初思ってたのですが、コンパイルしてしまう方法の方がよっぽどスマートでした

*2 : ^を使った正規表現式を書けばいいのですが、正規表現の式が2倍になるより、予め前処理した方が効率的なのでこうしています

*3 : /e は置換文を実行するオプション。/gは連続置換するオプション。/sは改行を含めてマッチングするオプション。

*4 : 知らなかった

2006/07/28(金)map関数の使い方

perl の map 関数の使い方

map関数の説明は他を参照してもらうとして。

$categories->[n] = { cat_ID  => カテゴリID, cat_name => カテゴリ名 };
$post2cat->[n]   = { post_id => 記事ID,  category_id => カテゴリID };

という2つのハッシュリファレンスからなる配列があったとき、記事ID→カテゴリ名という変換テーブル(ハッシュ)を作る方法。

my %cat2name  = map { $_->{cat_ID}  => $_->{cat_name} } @categories;
my %post2name =	map { $_->{post_id} => $cat2name{ $_->{category_id} } } @post2cat;

とすると、$post2name{記事ID} → カテゴリ名となります。でもこの場合、@post2cat の最後に出た要素が優先されるので、最初に出た要素を優先するために、

map { $post2name{ $_->{post_id} } ||= $cat2name{ $_->{category_id} } } @post2cat;

と書けます。

foreach(@ary) で回してるものは、mapに置き換えた方がスマートかつ効率的かも知れもません。

grep

grepは別に正規表現なだけではなく、格納条件を指定することもできるらしい。

my @newary = grep { $_ > 100 } @ary;

my @newary;
foreach(@ary) {
	if ($_ > 100) { push(@newary, @ary); }
}

と等価らしい。grep コマンドの印象が強かったから全然気づかなかった。

追記

んー便利だ。今まで foreach(@ary) で書いてたところが、いくつも書き直せそうだけど……まぁいいや。*1

とりあえず、まだ知らないことがありそうなのでPerl5の関数リストでも眺めておこう。

*1 : ハッカーのために可読性優先で書いてる部分もあったりするので。

2006/07/26(水)FileHandle vs Symbol

perl でオブジェクト指向を目指し、use strictしたプログラムを徹底していくと、どうにかしたくなるのが「ファイルハンドル」の存在です。

open(FD, "test.txt");
close(FD);

この FD をオブジェクトとして使い関数に対して引数として与えたりしたいのですが、use strict な環境では

my $fh = 'FD';
open($fh, "test.txt");
close($fh);

とやっても、エラーになってしまいます。かと言ってこのためだけに no strict refs; ともしたくない。またこの方法では、Perlをマルチスレッド動作させるとき、ファイルディスクリプタの名前空間が衝突し、ファイルが開けなくなる問題もあります。

ネットで情報を漁っていると、こういうときはファイルハンドルを動的生成する方法が紹介されています。

use FileHandle ();
my $fh = FileHandle->new();
open($fh, "test.txt");
close($fh);
use IO::File ();
my $fh = IO::File->new();
open($fh, "test.txt");
close($fh);

いずれの方法も実際うまく動きますし、動作上はなんの問題もありません。ただ、これらのモジュールはファイルハンドルを生成するためだけに使うにはリッチすぎます。つまり機能的すぎてモジュールのロードが遅いということです。*1

たた単純に、ファイルハンドルを動的生成したい場合は、

use Symbol ();
my $fh = Symbol::gensym();
open($fh, "test.txt");
close($fh);

とするのがベストだと思われます。

どれくらい速度が違うか、それぞれのサンプルだけのプログラムを作成し実行し、time コマンドで計測してみましたので参考までに。

実現方法実行速度
FileHandle100ms
IO::File94ms
Symbol23ms
直接指定14ms

こういう計測の結果、adiary では Symbol モジュールを使っています。

*1 : 逆に言えば、Socket通信のハンドルを作り、出力を自動的にフラッシュしたいときなどは FileHandle や IO::File モジュールを用いて $fh->autoflash(1) などとするのがベターです。

Perl 5.6.0以降限定、もっとよい方法

Perl 5.6.0からの新機能として、ファイルハンドルを自動で生成する機能があります

ハンドラを使用する関数 (open(), opendir(), pipe(), socketpair(), sysopen(), socket(), accept()) では、ファイルハンドルとして未定義のスカラ変数が与えられたとき、ファイルやディレクトリのハンドルを自動的に生成し変数に設定します。

と書かれています(意訳)。つまりどういうことかというと、上と同等のことをやるためには、

open(my $fh, "test.txt");
close($fh);

と書けば十分だということになります。こうすれば Symbol モジュールのロードは必要なくなり、メモリや実行時間の節約になります。ただし、これが実行できるのは5.6.0以降ですので、ソースの最初に

use 5.6.0;

と書いておくべきでしょう。adiaryでは現在(β7以降)この方法を使用しています。

2006/07/19(水)Perl CGIのキャッシュ環境

FastCGI対応は書き間違えだったのですが、どうせならということで対応してみました(対応版の配布はβ4以降になります)。

Perl CGIのキャッシュ動作比較

気づいたらあれこれ対応しすぎた感じがありますが、せっかくなのでどれが一番速いのか試してみしまた(笑) いずれの設定も全くチューニングも何もしていませんので、参考程度にお願いします

CPU  : Pentium3 800MHz (133*6/Coppermine)
OS   : FreeBSD 6.1-PRERELEASE
Web  : Apache 2.2 (prefork)
ETC  : DBキャッシュON
TEST : ab -n100 -c4 http://127.0.0.1/xxx/adiary.cgi/user/

テストしたページには適当な日記が数件表示されています。結果は数回実行して頻出値に近い値を取りました。

動作モードReq/secms/Req
cgi2.10477.270
speedycgi15.1965.825
mod_speedycgi26.4137.863
mod_fastcgi21.2846.990
mod_fcgid21.2147.157
mod_perl2*126.6937.473
text-file196.75.084

mod_fastcgiとmod_fcgidは差がなくて、mod_perl/mod_speedycgiが一歩前に出てるという感じですね。worker動作(スレッドモデル)となると、対応しているのは mod_perl2 vs mod_fcgid だけ。mod_perl2 はいかんせん導入が面倒くさいので、手軽さでは mod_fcgid の方がよいのかもしれません。

本格的にパフォーマンスを求めたり、高負荷時のメモリ消費量の少なさを考えると mod_perl2 on worker MPM に優る選択肢はないのですが個人では必要ないでしょう。*2

ただ、どれも Apache にモジュールを組み込まないとならないので、お手軽に高速化したい場合はSpeedyCGIソースコード)をオススメします。パフォーマンスも(個人で使うには)十分ですし、Apacheからは完全にcgiとして見えるので(プロセスが完全に分離するので)、精神的にもよいです。

誰か同じことをMTの管理画面で試さないかなぁ(笑)

*1 : Apache::Reloadなどのハンドラを外してやれば1ms程度改善しました

*2 : メモリ消費量を計測したわけではありませんが、mod_perl2/worker ではPerl自体もスレッド動作になるので、1リクエスト=1プロセスの mod_fcgid 系では勝負にならないと思います

各方式の特徴

せっかくなので、簡単に仕組みと特徴を説明。

Perl/cgi

通常のcgi動作。Apacheが、該当のcgiプログラムを fork し実行します。Perl/cgiでは、cgiファイル(使用ライブラリファイル群)のコンパイル時間がとても長く、度々問題となります。*3以下の方式は、どれもこのコンパイル時間を減らすことで大きな高速化を果たしています。

SpeedyCGI(=PersistentPerl)/mod_speedycgi

Apacheからみたら通常のcgiプログラムですが、スクリプト1行目にPerlではなくSpeedyCGIを起動するための

#!/usr/bin/speedy

を記述することでSpeedyCGIが動作します*4。SpeedyCGIは内部的にPerlを呼び出し、実行終了後もそのプロセスを常駐させ(バックエンドと言う)、続いてリクエストが来たときに空いたバックエンドがあればそれに処理を行わせます。

スクリプト側から見た場合、Apacheプロセスと分離されることを除けば*5、全体的な動作はほとんどmod_perlと同じです。

歴史が浅いのか何なのかいまいちマイナーだけど、導入も容易で、mod_perlでは動作しないスクリプトも動作し、Apacheとは別プロセスになるので使うのも気楽です。SpeedyCGIをインストールしてあるレンタルサーバもみかけます。

mod_speedycgiはSpeedyCGIの管理プログラム起動コストを押さえるためApacheにモジュールとして組み込んだものです。それによりかなり高速化されますが、worker MPMには非対応なので注意が必要です。

FastCGI/mod_fcgid

SpeedyCGI的な考え方をより推し進めて、Apache内部にバックエンドプロセスを管理する機能をモジュールとしてインストールしたものです。SpeedyCGIでは、いくらバックエンドがあるとはいえ結局は別プロセスである SpeedyCGI(/usr/bin/speedyそのもの)を fork して実行する必要がありましたが、FastCGIではバックエンドを直接操作するため fork のオーバーヘッドがありません。

mod_fastcgi(FastCGI)とmod_fcgidの差ですが、前者をスレッド動作のApacheに組み込むのはあまりよろしくないとのことです。また巷の噂によると後者の方が速いとかなんとか。なお、FastCGIをスレッド動作のApacheに組み込んだとしても、CGIプログラム(Perlなど)自体は1プロセス=1クライアントとなります。

cgiスクリプトはFastCGI向けに改造する必要があります。

use FCGI;
my $count = 0;
my $request = FCGI::Request();
while($request->Accept() >= 0) {
	print("Content-type: text/html\r\n\r\n", ++$count);
}

mod_perl

バックエンドどころか、「Perl自体をApacheに抱え込んでしまえばいい」という荒技的な解決策を提示するのが、mod_perl。荒技なだけに、もっともオーバーヘッドが少ない*6方式です。mod_perlの特徴として、Apacheの内部動作をスクリプト側から事細かに制御できますが、移植性(配布性)を考えたソフトではあまり使うことはありません*7

mod_perlには、Apache 1.x系向けのいわゆるmod_perl(以下mod_perl1と表記)と、Apache 2.0/2.2系向けのmod_perl2があり全く互換性がありません*8。この互換性が度々問題となって、mod_perl1向け書かれたcgiをmod_perl2で動かそうとすると誤動作して厄介な問題を引き起こします。その代表選手とも言えるものがMovable Type*9

スクリプト側から見た場合、mod_perl1やスレッド動作でないApache2ならば、およそSpeedyCGIと同じような感じです。ただ、Apache2が真価を発揮する worker(スレッド)動作でのmod_perl2は、Perlスクリプト自体もスレッド動作することで高い効率が得られる一方、カレントディレクトリを指定出来ない(chdir()できない)こと*10、あらゆるスクリプトがマルチスレッドでライブラリ空間(メモリ空間)を共有すること*11が大きなネックになります。後からこの対応を行うことは困難であり、これがmod_perl2向けスクリプトがほとんど存在しない原因になっていると思われます。

余談

Apache2(Prefock) + mod_perl2 のときは、次のようにすると自動で chdir されます(いずれか1つ選択)。

PerlResponseHandler ModPerl::RegistryPrefork
PerlResponseHandler ModPerl::PerlRunPrefork

*3 : 不必要なライブラリを極力ロードしないように気を付けているadiaryですら、cgi実行時間の約9割がPerlのコンパイル時間です。

*4 : この仕組みから分かるとおり、SpeedyCGIは何もcgiだけではなく、通常コンソールから実行するPerlスクリプトに対しても高速化が可能

*5 : Apache2のworkerであっても、chdirやumaskなどプロセス環境を変更することができる

*6 : 原理上もっとも速く動作する

*7 : Apache自体の動作を変えてしまうような、特殊なことをする専用アプリ開発には向いている

*8 : Apache2では、スレッド動作による効率化を求めたため、Apacheと一体となる mod_perl はApache 2系向けに大幅な改変が施されている

*9 : パッチを充てたり、mod_perl2の設定をあれこれ変更したりと涙ぐましい努力がたくさん見られます

*10 : サーバ全体で1つディレクトリにあるmod_perl2スクリプトしか実行しないとすれば、なんとかなるかも知れませんが……。きわどい。

*11 : 特定関数内で一時的であってもグローバル変数にデバッグフラグを立てるモードを設定するなどということが出来ない。

2006/06/12(月)定義済の関数を調べる

can メソッドの罠

Perlのマニュアルによれば、オブジェクトのメソッドを調べるには、すべてのクラスで継承される UNIVERSAL クラスの can メソッドを用いて

if ($obj->can("function_name")) { $obj->function_name(); }
if (MYCLASS->can("function_name")) { MYCLASS->function_name(); }

としろと書いてあります。実際この判定はうまく動き、スケルトンシステム(Satsukiシステム)が今ほど本格的になる前のテンプレートに少し毛が生えた程度のころは、この can メソッドを使って関数呼び出しと変数の判別をしていました*1

この can メソッドには罠があって、オートローダー(AUTOLOAD 関数/メソッド)が定義されているときcanメソッドは常に true を返します。AUTOLOAD というのは、もともとはPerlの関数を必要なときに動的ロードする仕組みであって、Satsukiシステムでは独自のオートローダーを定義しています*2。さらに、呼び出された関数が未定義のときに呼び出されるという仕組みを用いて、あたかも関数のような動作を動的生成ということも可能です。AUTOLOADは、未定義関数をロードして代理で呼び出すなり、あたかもその関数が存在するように振る舞うなりすればいいということになります。

# オブジェクト内のハッシュを返すメソッド
# 呼び出した未定義関数はグローバル変数「our $AUTOLOAD」に入っている。
sub AUTOLOAD {
	# $AUTOLOAD='MYCLASS::FUNCTION' → $func='FUNCTION'
	my $func = substr($AUTOLOAD, rindex($AUTOLOAD, '::')+2);
	my $self = shift;
	return $self->{$func};
}

*1 : この時代は、存在する関数名と同じ変数名を使えなかったんですね……懐かしい

*2 : なぜそんな仕組みが必要になるかと言うと、perlの実行で一番遅いのはperlのソースをperlインタプリタが実行可能な形式にコンパイルする処理でして、普段は滅多に使用しないようなデータベース初期化関数やら日記帳を削除する関数やらを常にロードしてコンパイルするのはcgi動作を考えたときに大きなロスとなります。

canメソッドが正常に働くとき、働かないとき

Satsukiシステムのオートローダーは、

Auth.pm
Auth_auto.pm
Auth_auto2.pm

という具合に、目的の関数が見つかるまで次々と連番のモジュールをロードしていくという方式で*3、AUTOLOAD内で次々とモジュールをロードしながら、canメソッドを使用して目的の関数が見つかったか調べる方法を取っていました。そして見つからなかったら失敗(die)するように作ってありました。

実際この方式はうまく動作していまして、このことからAUTOLOAD関数が存在するクラス内のcanはそのAUTOLOAD関数内では正常に判別できることがわかります。たぶんperl自身がcanの中でこの辺をこまめに判別しているようです。

*3 : というのも、関数1つを1つのファイル分割する標準AutoLoaderモジュールでは、いかんせん1つ1つをロードするオーバーヘッドが大きすぎる

AUTOLOADの暴走(無限ループ)

しかし更にややこしい問題が発生しました。SatsukiシステムのDBモジュール*4は、そのAPIが規定されていることが利点となり、どのような下位モジュールに対してもキャッシュを行うDB_cache.pmというものが存在します。この中では、select のみオーバーライドしキャッシュを行い、その他の関数はcanで調べてその関数が存在するときは下位のモジュールの関数を呼び出すという仕組みにしていました。この下位モジュールを呼び出す仕組みでAUTOLOADを使用していたのです。

分かりずらいので図式すると、今このサーバではPostgreSQLおよびこのキャッシュモジュールを使用しています。このときデータベースへのアクセスは、

(キャッシュなり)Diary.pm → DB_pg.pm
(キャッシュあり)Diary.pm → DB_cache.pm → DB_pg.pm

となっています。Diary.pm からは、キャッシュがあろうがなかろうが、DBモジュールAPIを満たすものに対するアクセスしか行っていないため何ら違いはありません。

ここで、プログラム中にたまたま Diary.pm から DB_pg.pm に存在しない関数xyz()を読んでしまったのが問題の始まりでした*5

モジュールAUTOLOAD内
DB_cache.pmDB_pg.pm に xyz() が存在しないのに xyz() を呼び出している
DB_pg.pmxyz() のロードに成功していないのに*6、なぜかxyz()が存在すると思って呼びに行く

結果として「DB_cache.pm → DB_pg.pm → DB_cache.pm → …」という無限ループに陥ってしまいました。DB_cache はその仕組み上、DB_pg.pm を動的に継承してしまうため、DB_pg への関数呼び出しは、例えDB_pg自身からであってもすべて DB_cache を経由します。それでこういうことが起こったわけです。

しかし、どちらのモジュールでも、こういうことを避けるために「canメソッドによって関数が実在するか調べていた」ハズです。調べてみると、どちらのcanも、どんなメソッドに対しても成功していました。ありもしないでたらめな関数でもなんでもif ($obj->can("xxx"))が成立していたわけです。

これが最初に言った、AUTOLOAD関数が定義されているときcanメソッドは常に true を返すという罠です。オブジェクトを動的に継承したために(実際に継承するのではなく、あたかも継承したようにみせかけた*7ために)、自分のクラス外のcanが常に成功したわけです。

*4 : adiaryでも使用

*5 : mod_perl環境で無限ループに陥るとhttpdプロセスと同一であることが災いして、httpdがふくれあがりシステムのリソースを際限なく食いつぶします(汗)

*6 : 存在しないのだから当たり前

*7 : これはいかなるDB下位モジュールに対しても、汎用的にキャッシュモジュールを挟める仕組みを作るためにどうしても必要

解決策

はじめからcanなんて必要なかったんじゃないかというくらい、実に簡単な解決策でした。DB_pg.pm、DB_cache.pmをそれぞれ次のようにしました。

sub AUTOLOAD {
	(略)
	if (defined &$AUTOLOAD) { $can=1; last; }
sub can {
	my ($self, $func) = @_;
	my $func = $self->{_super_class} . '::' . $func;
	return (defined &$func);
}

そしてたぶん、この方法の方が速く動作します。

注意

ここでは便宜上DB_pg.pm内に AUTOLOAD が存在するとして説明しましたが、実際には、Autoloader.pmからAUTOLOAD関数をインポートしています。

OK キャンセル 確認 その他