2006年12月08日 11:00 [Edit]

perl - Devel::Leak

camel

そんなあなたに、Devel::Leak

Rauru Blog ? Blog Archive ? 循環参照
ところが 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


この記事へのトラックバックURL