2006年12月08日 11:00 [Edit]
perl - Devel::Leak
そんなあなたに、Devel::Leak。
ところが Scalar::Util 使って循環参照になってるとおぼしき変数を全部 weaken してみたんだけど、DESTORY されるかどうか見てみると、1サイクル終わっても1つだけ解放されないオブジェクトが残ってる。どうしちゃるかねとゆうわけで、今度は Devel::Cycle を CPAN から取って来てインストール。ところがこれ、参照元側のオブジェクトを1個指定して循環参照を検索するもんなんですね。どれが参照元なのかわからないので、やっぱり見つからないー。
ただし、肝心のLeakしているオブジェクトまで表示させるには、-DDEBUGGING付きでcompileしたperlが必要です。確認するには、
perl -Ds -le 'print 1+2*3/4**5'
とかとすればOK。
EXECUTING...
=>
=>
=>
=> *
=> * PVMG("-e"\0)
-e
=> SV_YES
dankogai@dan-mbpro15[1168]:~/blog/perl% perl -Ds -le 'print 1+2*3/4**5'
=>
=> IV(2)
=> IV(2) IV(3)
=>
=> IV(4)
=> IV(4) IV(5)
=>
=> IV(6)
=> IV(6) PVNV(1024)
=>
=> IV(1)
=> IV(1) NV(0.00585938)
EXECUTING...
=>
=>
=>
=> *
=> * NV(1.00586)
1.005859375
=> SV_YES
と出力されればOK。
Recompile perl with -DDEBUGGING to use -D switch (did you mean -d ?) 1.005859375
と出たら、それ用のperlを用意してください。Devel::Leak抜きでも-DDEBUGGINGは何かと便利なので、自分でperlをbuildしている人は必ず付けるようにするとよいでしょう。
で、肝心のDevel::Leakです。テスト用にこんなコードを用意してみます。
#!/usr/local/bin/perl
use strict;
use warnings;
use Devel::Leak;
use Scalar::Util;
my $handle;
{
package Self::Bookmarker;
sub new{
my $class = shift;
bless { @_ }, $class;
}
sub selkma {
my $self = shift;
$self->{self} = \$self;
Scalar::Util::weaken $self->{self}; # leak stopper
$self;
}
my $dummy = Self::Bookmarker->new();
}
my $then = Devel::Leak::NoteSV($handle);
{
my $dan = Self::Bookmarker->new(id => 'dankogai');
$dan->selkma;
}
my $now = Devel::Leak::CheckSV($handle);
warn "then:$then; now:$now; ", $now - $then, " objects remain.";
まずはLeakがない状態。
then:3002; now:3002; 0 objects remain. at leak.pl line 30.
それでは、実際にLeakさせてみましょう。Scalar::Util::weaken $self->{self};をコメントアウトして実行してみます。
new 0x182ffc4 : SV = PV(0x18020c0) at 0x182ffc4 REFCNT = 1 FLAGS = (POK,pPOK) PV = 0x660720 "dankogai"\0 CUR = 8 LEN = 12 new 0x182ffd0 : SV = NULL(0x0) at 0x182ffd0 REFCNT = 1 FLAGS = (PADBUSY,PADMY) new 0x182ffdc : SV = RV(0x1816468) at 0x182ffdc REFCNT = 1 FLAGS = (ROK) RV = 0x182e2ac new 0x180127c : SV = PVHV(0x1805d50) at 0x180127c REFCNT = 1 FLAGS = (OBJECT,SHAREKEYS) IV = 2 NV = 0 STASH = 0x1801b10 "Self::Bookmarker" ARRAY = 0x660730 (0:6, 1:2) hash quality = 125.0% KEYS = 2 FILL = 2 MAX = 7 RITER = -1 EITER = 0x0 then:2999; now:3003; 4 objects remain. at leak.pl line 30.
たしかにLeakが検出されました。今度はさらに$dan->selkma;をコメントアウトしてみましょう。
then:2997; now:2997; 0 objects remain. at leak.pl line 30.
Leakはないようです。これでSelf::Bookmarker::selkma()がLeakの犯人だとわかったわけです。
少し面白いものもお目にかけましょう。一旦ソースを下に戻した上で、今度はmy $dummy = Self::Bookmarker->new();をコメントアウトしてみます。するとあら不思議、
new 0x182ff70 : SV = NULL(0x0) at 0x182ff70
REFCNT = 1
FLAGS = ()
new 0x182ff7c : SV = PVGV(0x660390) at 0x182ff7c
REFCNT = 1
FLAGS = (GMG,SMG,MULTI)
IV = 0
NV = 0
MAGIC = 0x660400
MG_VIRTUAL = &PL_vtbl_glob
MG_TYPE = PERL_MAGIC_glob(*)
MG_OBJ = 0x182ff7c
NAME = "DESTROY"
NAMELEN = 7
GvSTASH = 0x1801378 "UNIVERSAL"
GP = 0x6603d0
SV = 0x182ff88
REFCNT = 1
IO = 0x0
FORM = 0x0
AV = 0x0
HV = 0x0
CV = 0x0
CVGEN = 0x0
GPFLAGS = 0x0
LINE = 23
FILE = "leak.pl"
FLAGS = 0x2
EGV = 0x182ff7c "DESTROY"
new 0x182ff88 : SV = NULL(0x0) at 0x182ff88
REFCNT = 1
FLAGS = ()
new 0x182ff94 : SV = PVGV(0x660430) at 0x182ff94
REFCNT = 1
FLAGS = (GMG,SMG,MULTI)
IV = 0
NV = 0
MAGIC = 0x6604a0
MG_VIRTUAL = &PL_vtbl_glob
MG_TYPE = PERL_MAGIC_glob(*)
MG_OBJ = 0x182ff94
NAME = "AUTOLOAD"
NAMELEN = 8
GvSTASH = 0x1801b10 "Self::Bookmarker"
GP = 0x660470
SV = 0x182ffa0
REFCNT = 1
IO = 0x0
FORM = 0x0
AV = 0x0
HV = 0x0
CV = 0x0
CVGEN = 0xa8
GPFLAGS = 0x0
LINE = 23
FILE = "leak.pl"
FLAGS = 0x2
EGV = 0x182ff94 "AUTOLOAD"
new 0x182ffa0 : SV = NULL(0x0) at 0x182ffa0
REFCNT = 1
FLAGS = ()
new 0x182ffac : SV = PVGV(0x6604d0) at 0x182ffac
REFCNT = 1
FLAGS = (GMG,SMG,MULTI)
IV = 0
NV = 0
MAGIC = 0x660540
MG_VIRTUAL = &PL_vtbl_glob
MG_TYPE = PERL_MAGIC_glob(*)
MG_OBJ = 0x182ffac
NAME = "AUTOLOAD"
NAMELEN = 8
GvSTASH = 0x1801378 "UNIVERSAL"
GP = 0x660510
SV = 0x182ffb8
REFCNT = 1
IO = 0x0
FORM = 0x0
AV = 0x0
HV = 0x0
CV = 0x0
CVGEN = 0x0
GPFLAGS = 0x0
LINE = 23
FILE = "leak.pl"
FLAGS = 0x2
EGV = 0x182ffac "AUTOLOAD"
new 0x182ffb8 : SV = NULL(0x0) at 0x182ffb8
REFCNT = 1
FLAGS = ()
new 0x180109c : SV = PVGV(0x660320) at 0x180109c
REFCNT = 1
FLAGS = (GMG,SMG,MULTI)
IV = 0
NV = 0
MAGIC = 0x6602f0
MG_VIRTUAL = &PL_vtbl_glob
MG_TYPE = PERL_MAGIC_glob(*)
MG_OBJ = 0x180109c
NAME = "DESTROY"
NAMELEN = 7
GvSTASH = 0x1801b10 "Self::Bookmarker"
GP = 0x660360
SV = 0x182ff70
REFCNT = 1
IO = 0x0
FORM = 0x0
AV = 0x0
HV = 0x0
CV = 0x0
CVGEN = 0xa8
GPFLAGS = 0x0
LINE = 23
FILE = "leak.pl"
FLAGS = 0x2
EGV = 0x180109c "DESTROY"
new 0x1801180 : SV = IV(0x180f804) at 0x1801180
REFCNT = 1
FLAGS = (IOK,pIOK)
IV = 25172752
then:2988; now:2997; 9 objects remain. at leak.pl line 27.
と出てくるではありませんか。
これはLeakではありません。Devel::Leakは、Devel::Leak::NoteSV()からDevel::Leak::CheckSV()までに存在しなかったSV、すなわちオブジェクトを全て洗い出します。まだSelf::Bookmarkerが一度も使われていない状態では、AUTOLOADなども当然未定義。Perlはこれらの未定義メソッドも、最初に使われた時点でno-opに「初期化」しているのです。だからDevel::Leak::NoteSV()を呼び出す前に一度ダミーオブジェクトを作って消しとしたわけです。
今は亡きNick Ing-Simmonsに改めて感謝を。
Dan the Perl Monger