Masteries

技術的なことや仕事に関することを書いていきます.

Perlのガベージコレクションについて

Perlでは, ガベージコレクション(GC)について「リファレンスカウント方式(参照カウント方式)」という仕組みを利用しています. リファレンスカウント方式は, 全てのデータに対して「参照カウント」と呼ばれる整数値を隠しパラメータを持たせ, そのデータが参照されている数を格納し, これが0になった時にデータを破棄する... というGCの仕組みです.

この記事では, PerlのGCとその根幹である「リファレンスカウント」について, 本当にざっくり説明していきます.

リファレンスカウントを確認する

ある変数に紐付いているデータ(つまりは, 変数の中身)のリファレンスカウントは, Devel::Peekモジュールを利用すれば確認できます.

use strict;
use warnings;
use Devel::Peek;

my $foo = 1; #
             # (1)
Dump $foo;   # 

my $bar = \$foo; #
                 # (2)
Dump $foo;       #

undef $bar; #
            # (3)
Dump $foo;  #

このコードを実行すると, 結果は次のようになります.

$ perl refcnt.pl
SV = IV(0x7fe27b009968) at 0x7fe27b009978 # (1)の結果
  REFCNT = 1
  FLAGS = (PADMY,IOK,pIOK)
  IV = 1
SV = IV(0x7fe27b009968) at 0x7fe27b009978 # (2)の結果
  REFCNT = 2
  FLAGS = (PADMY,IOK,pIOK)
  IV = 1
SV = IV(0x7fe27b009968) at 0x7fe27b009978 # (3)の結果
  REFCNT = 1
  FLAGS = (PADMY,IOK,pIOK)
  IV = 1

Devel::Peekを利用すると, このようにPerlの変数の内部構造を見ることが出来ます(この辺りの知識は, XSモジュール(C言語を利用したPerlモジュール)の開発をしないのであれば, そこまでしっかり理解しに行く必要はほとんどないです). この中の「REFCNT」というのが, その変数に格納されているデータのリファレンスカウントを示しています.

...詳しく見て行きましょう. まず(1)ですが, $fooという変数を宣言し, その中に1というデータを格納しました. そのため, このときの$fooが格納しているデータへのREFCNTは1です($fooが参照している).

次に(2)では, $fooというスカラ変数のリファレンスを$barに代入しています. これによって, $fooという変数に格納されているデータは, リファレンスを通して$barからも参照できるようになりました. そのため, $fooが格納しているデータへのREFCNTは2に増えています.

最後に(3)では, $barという変数に格納された$fooへのリファレンスを, undef関数を利用してundefに置き換えています. そのため, $fooという変数に格納されているデータへの参照は, 1つ減ってしまったので, REFCNTは1になっている訳です.

リファレンスカウントとスコープ

さて, コードを少し書き換えてみます. 先程のコードの(2)にあたる部分を, スコープ({}の間)内に記述するようにしました.

use strict;
use warnings;
use Devel::Peek;

my $foo = 1;

{
    my $bar = \$foo;

    Dump $foo; # (1)
} # ※

Dump $foo; # (2)

実行結果は次の通りです.

SV = IV(0x7fd3a4802f68) at 0x7fd3a4802f78 # (1)
  REFCNT = 2
  FLAGS = (PADMY,IOK,pIOK)
  IV = 1
SV = IV(0x7fd3a4802f68) at 0x7fd3a4802f78 # (2)
  REFCNT = 1
  FLAGS = (PADMY,IOK,pIOK)
  IV = 1

先程の例と同じく, (1)では$fooによる参照と, そこへのリファレンスを持った$barによる参照があるので, $fooに格納されたデータのREFCNTは2です. 一方, 次の(2)では, REFCNTは1に減ってしまっています. これは, $barがスコープを抜けた段階(※の部分)で廃棄され, これによって$barが持っているデータ($fooへのリファレンス)のREFCNTが0になって破棄され, 結果として$fooに格納されているデータへの参照が$fooという変数からの参照のみになってしまったので, REFCNTが1減っている訳です.

よく, Perlの解説で「スコープを抜けた時点で, 変数は破棄されます」という説明がありますが, その際にPerlの内部ではこのような処理が行われ, 変数とそれに紐づくデータが破棄されている訳です.

まとめ

Perlのガベージコレクションと, その根幹を担う「リファレンスカウント」という仕組みを紹介しました. Qiita:Teamにサクっと書いたテキストをサルベージしたものなので, 文中に間違い等々あるかもしれません. その際はぜひご指摘いただければ嬉しいです...!