Perlのメモリリークを見つける方法
Perlではメモリリーク検出ツールがいくつか開発されているので、top(1)の結果を眺めるよりそういうツールを使うほうが楽である。
さて、メモリリークが発生しているとき、その可能性としてはだいたい以下の4つが挙げられる。
この1-3についてはすべてPerlインタプリタ内の出来事であり、Test::LeakTraceを使って検出できる。4を検出するのは難しいが、Test::Valgrindが役に立つ。
Test::LeakTraceのSYNOPSISは歴史的経緯によりごちゃごちゃしているが、テストで使うべき関数はno_leaks_ok()
とleaks_cmp_ok()
だけである。
たとえば、以下のようにして使う*2。
#!perl # Usage: perl leaktrace.pl [--weak] use 5.14.0; use warnings; use Test::LeakTrace; use Test::More; package LinkedList { use Mouse; use MouseX::StrictConstructor; has next => ( is => 'rw', isa => 'Maybe[LinkedList]', ); has previous => ( is => 'rw', isa => 'Maybe[LinkedList]', (scalar grep { $_ eq '--weak' } @ARGV) ? (weak_ref => 1) : (), ); has value => ( is => 'rw', ); __PACKAGE__->meta->make_immutable(); } my $root = LinkedList->new( value => 10 ); $root->next( LinkedList->new( previous => $root, value => 20 ) ); say $root->dump(); no_leaks_ok { my $root = LinkedList->new( value => 10 ); $root->next( LinkedList->new( previous => $root, value => 20 ) ); }; done_testing; __END__
出力はDevel::Peek形式でのダンプで出すのでリークする値が多いと以下のとおり非常に煩雑だが、無事にメモリリークが検出できた。またメモリリークが発生した箇所も示してくれる。これはXSが絡むと正確でない可能性があるが、Perlレベルではほぼ正確だと思う*3。
$ perl leaktrace.pl $VAR1 = bless( { next => bless( { previous => $VAR1, value => 20 }, 'LinkedList' ), value => 10 }, 'LinkedList' ); not ok 1 - leaks 6 <= 0 # Failed test 'leaks 6 <= 0' # at leaktrace.pl line 35. # '6' # <= # '0' # leaked SCALAR(0x9567280) from leaktrace.pl line 33. # 32:no_leaks_ok { # 33: my $root = LinkedList->new( value => 10 ); # 34: $root->next( LinkedList->new( previous => $root, value => 20 ) ); # SV = IV(0x956727c) at 0x9567280 # REFCNT = 1 # FLAGS = (IOK,pIOK) # IV = 10 # leaked HASH(0x96bbe00) from leaktrace.pl line 33. # 32:no_leaks_ok { # 33: my $root = LinkedList->new( value => 10 ); # 34: $root->next( LinkedList->new( previous => $root, value => 20 ) ); # SV = PVHV(0x963b7e0) at 0x96bbe00 # REFCNT = 1 # FLAGS = (OBJECT,SHAREKEYS) # STASH = 0x95805e8 "LinkedList" # ARRAY = 0x973ba68 (0:6, 1:2) # hash quality = 125.0% # KEYS = 2 # FILL = 2 # MAX = 7 # RITER = -1 # EITER = 0x0 # Elt "next" HASH = 0x4bcd2941 # SV = IV(0x9617dc4) at 0x9617dc8 # REFCNT = 1 # FLAGS = (ROK) # RV = 0x97282b8 # SV = PVHV(0x963b7f0) at 0x97282b8 # REFCNT = 1 # FLAGS = (OBJECT,SHAREKEYS) # STASH = 0x95805e8 "LinkedList" # ARRAY = 0x9701c20 (0:6, 1:2) # hash quality = 125.0% # KEYS = 2 # FILL = 2 # MAX = 7 # RITER = -1 # EITER = 0x0 # Elt "previous" HASH = 0x6f62f028 # SV = IV(0x9617e84) at 0x9617e88 # REFCNT = 1 # FLAGS = (ROK) # RV = 0x96bbe00 # SV = PVHV(0x963b7e0) at 0x96bbe00 # REFCNT = 1 # FLAGS = (OBJECT,OOK,SHAREKEYS) # STASH = 0x95805e8 "LinkedList" # ARRAY = 0x9618e58 (0:6, 1:2) # hash quality = 125.0% # KEYS = 2 # FILL = 2 # MAX = 7 # RITER = 1 # EITER = 0x966035c # Elt "value" HASH = 0x1e720953 # SV = IV(0x9728284) at 0x9728288 # REFCNT = 1 # FLAGS = (IOK,pIOK) # IV = 20 # Elt "value" HASH = 0x1e720953 # SV = IV(0x956727c) at 0x9567280 # REFCNT = 1 # FLAGS = (IOK,pIOK) # IV = 10 # leaked REF(0x9617e88) from leaktrace.pl line 34. # 33: my $root = LinkedList->new( value => 10 ); # 34: $root->next( LinkedList->new( previous => $root, value => 20 ) ); # 35:}; # SV = IV(0x9617e84) at 0x9617e88 # REFCNT = 1 # FLAGS = (ROK) # RV = 0x96bbe00 # SV = PVHV(0x963b7e0) at 0x96bbe00 # REFCNT = 1 # FLAGS = (OBJECT,OOK,SHAREKEYS) # STASH = 0x95805e8 "LinkedList" # ARRAY = 0x9618e58 (0:6, 1:2) # hash quality = 125.0% # KEYS = 2 # FILL = 2 # MAX = 7 # RITER = -1 # EITER = 0x0 # Elt "next" HASH = 0x4bcd2941 # SV = IV(0x9617dc4) at 0x9617dc8 # REFCNT = 1 # FLAGS = (ROK) # RV = 0x97282b8 # SV = PVHV(0x963b7f0) at 0x97282b8 # REFCNT = 1 # FLAGS = (OBJECT,OOK,SHAREKEYS) # STASH = 0x95805e8 "LinkedList" # ARRAY = 0x9702e00 (0:6, 1:2) # hash quality = 125.0% # KEYS = 2 # FILL = 2 # MAX = 7 # RITER = -1 # EITER = 0x0 # Elt "previous" HASH = 0x6f62f028 # SV = IV(0x9617e84) at 0x9617e88 # REFCNT = 1 # FLAGS = (ROK) # RV = 0x96bbe00 # Elt "value" HASH = 0x1e720953 # SV = IV(0x956727c) at 0x9567280 # REFCNT = 1 # FLAGS = (IOK,pIOK) # IV = 10 # leaked REF(0x9617dc8) from leaktrace.pl line 34. # 33: my $root = LinkedList->new( value => 10 ); # 34: $root->next( LinkedList->new( previous => $root, value => 20 ) ); # 35:}; # SV = IV(0x9617dc4) at 0x9617dc8 # REFCNT = 1 # FLAGS = (ROK) # RV = 0x97282b8 # SV = PVHV(0x963b7f0) at 0x97282b8 # REFCNT = 1 # FLAGS = (OBJECT,OOK,SHAREKEYS) # STASH = 0x95805e8 "LinkedList" # ARRAY = 0x9702e00 (0:6, 1:2) # hash quality = 125.0% # KEYS = 2 # FILL = 2 # MAX = 7 # RITER = -1 # EITER = 0x0 # Elt "previous" HASH = 0x6f62f028 # SV = IV(0x9617e84) at 0x9617e88 # REFCNT = 1 # FLAGS = (ROK) # RV = 0x96bbe00 # SV = PVHV(0x963b7e0) at 0x96bbe00 # REFCNT = 1 # FLAGS = (OBJECT,OOK,SHAREKEYS) # STASH = 0x95805e8 "LinkedList" # ARRAY = 0x9618e58 (0:6, 1:2) # hash quality = 125.0% # KEYS = 2 # FILL = 2 # MAX = 7 # RITER = -1 # EITER = 0x0 # Elt "next" HASH = 0x4bcd2941 # SV = IV(0x9617dc4) at 0x9617dc8 # REFCNT = 1 # FLAGS = (ROK) # RV = 0x97282b8 # Elt "value" HASH = 0x1e720953 # SV = IV(0x9728284) at 0x9728288 # REFCNT = 1 # FLAGS = (IOK,pIOK) # IV = 20 # leaked HASH(0x97282b8) from leaktrace.pl line 34. # 33: my $root = LinkedList->new( value => 10 ); # 34: $root->next( LinkedList->new( previous => $root, value => 20 ) ); # 35:}; # SV = PVHV(0x963b7f0) at 0x97282b8 # REFCNT = 1 # FLAGS = (OBJECT,OOK,SHAREKEYS) # STASH = 0x95805e8 "LinkedList" # ARRAY = 0x9702e00 (0:6, 1:2) # hash quality = 125.0% # KEYS = 2 # FILL = 2 # MAX = 7 # RITER = -1 # EITER = 0x0 # Elt "previous" HASH = 0x6f62f028 # SV = IV(0x9617e84) at 0x9617e88 # REFCNT = 1 # FLAGS = (ROK) # RV = 0x96bbe00 # SV = PVHV(0x963b7e0) at 0x96bbe00 # REFCNT = 1 # FLAGS = (OBJECT,OOK,SHAREKEYS) # STASH = 0x95805e8 "LinkedList" # ARRAY = 0x9618e58 (0:6, 1:2) # hash quality = 125.0% # KEYS = 2 # FILL = 2 # MAX = 7 # RITER = -1 # EITER = 0x0 # Elt "next" HASH = 0x4bcd2941 # SV = IV(0x9617dc4) at 0x9617dc8 # REFCNT = 1 # FLAGS = (ROK) # RV = 0x97282b8 # SV = PVHV(0x963b7f0) at 0x97282b8 # REFCNT = 1 # FLAGS = (OBJECT,OOK,SHAREKEYS) # STASH = 0x95805e8 "LinkedList" # ARRAY = 0x9702e00 (0:6, 1:2) # hash quality = 125.0% # KEYS = 2 # FILL = 2 # MAX = 7 # RITER = 0 # EITER = 0x95dddfc # Elt "value" HASH = 0x1e720953 # SV = IV(0x956727c) at 0x9567280 # REFCNT = 1 # FLAGS = (IOK,pIOK) # IV = 10 # Elt "value" HASH = 0x1e720953 # SV = IV(0x9728284) at 0x9728288 # REFCNT = 1 # FLAGS = (IOK,pIOK) # IV = 20 # leaked SCALAR(0x9728288) from leaktrace.pl line 34. # 33: my $root = LinkedList->new( value => 10 ); # 34: $root->next( LinkedList->new( previous => $root, value => 20 ) ); # 35:}; # SV = IV(0x9728284) at 0x9728288 # REFCNT = 1 # FLAGS = (IOK,pIOK) # IV = 20 1..1 # Looks like you failed 1 test of 1.
実際のコードは以下のとおり。