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.実際のコードは以下のとおり。