この間もメモリリークを通報してくれたところからまたメモリリークで困っているという通報が来たので、チェック。普段は$csv->parse(); $csv->fields()を使っているのだけれども、どうもパフォーマンス的にgetline()のほうが速いのでそちらを使いたいとのこと。

まず以下のテストコードを動かしてメモリを観察
use strict; use blib; use Text::CSV_XS 0.36; print "My PID: $$\n"; my $line = join("\t", ('a'..'z')) . "\n"; my $csv = Text::CSV_XS->new({ sep_char => "\t" }); my $iter = 10000; my $data = $line x $iter; my $count = 0; while(1) { open(my $io, '<', \$data); while(my $row = $csv->getline($io) ) { } $count += $iter; print "Did $count iterations\n"; sleep(2); }

これを自分のMacBookで見てると外側のwhileループを回す毎に600Kくらいずつメモリ使用量が増えて行く。リークしてるっぽいなぁ。

実はこの問題、ちょっと前にも見た事あったんですが、その時は特にひらめかなかったのです。今回もなんの気無しにText::CSV_XSのソースを見ていたら、リークしないparse()とgetline()を追って行くとどうも1個だけreturnされてるわけでもない、微妙に操作方法が違う変数があることに気づいた。

前のText::MeCabのエントリでも書いたけれども、Perlにはふたつメモリを解放するタイミングがある。そのうちのひとつでいわゆる一時変数が解放されるのだが、Perlでは一時変数も含めて、Perl内でデータとして扱えるもの全てのメモリ管理はリファレンスカウントで管理しているため、その調整を手動してやらないとプログラム終了時まで解放される事は無い。

でも一時変数まで全部律儀にそういうカウント云々していると面倒なので、Perl内部では"mortal"と呼ばれる変数が存在する。これらの変数は「今使い終わったら、次にチャンスがある時にメモリ解放される」という存在だ。これを設定するには、変数を作成後、sv_2mortal()という関数でフラグをつける:
SV *sv = sv_2mortal( newSVpv("Hello!", 6) ); AV *av = newAV(); sv_2mortal( (SV *) av);

これでこのCのセクションから抜けて、Perlインタプレタがチャンスがある時にメモリ解放が行われる。

さて、本題のText::CSV_XSだが、これがどうもある配列にされてなかった模様。以下パッチ
--- Text-CSV_XS-0.36/CSV_XS.xs 2008-03-06 00:26:41.000000000 +0900 +++ Text-CSV_XS-0.36.patched/CSV_XS.xs 2008-03-11 18:19:00.000000000 +0900 @@ -1083,6 +1083,7 @@ CSV_XS_SELF; av = newAV (); avf = newAV (); + sv_2mortal((SV *) avf); ST (0) = xsParse (hv, av, avf, io, 1) ? sv_2mortal (newRV_noinc ((SV *)av)) : &PL_sv_undef;

これでとりあえず先ほどのスクリプト上では問題がなくなったようなので、P5Pにメールを送っておいた。今のところレスポンスはないが・・・

#これだけ偉そうにしておいて間違ってたらどうしようと今軽く焦っている