RubyのGCを深掘りする(1)GC::INTERNAL_CONSTANTS(翻訳)
昔から「ゴミを見ればその人がわかる」と申しますが、プログラミング言語でも同じことが言えます。より正確には、プログラミング言語のガベージコレクションを調べると、そのプログラミング言語について多くのことを学べます。本記事は、RubyのGCシリーズ記事の第1回です(最終的には書籍にします)。
今回は(イマイチなメタファーですが)、Rubyのゴミ箱の中に必ずあるものをあさってみるところから始めます。RubyはGC::INTERNAL_CONSTANTS
をpublicにしているので、これを使ってガベージコレクタの各種定数を調べられます。
GC::INTERNAL_CONSTANTS
Ruby 3.0でGC::INTERNAL_CONSTANTS
を出力すると以下のようになります。
{
:DEBUG=>false,
:RVALUE_SIZE=>40,
:HEAP_PAGE_OBJ_LIMIT=>409,
:HEAP_PAGE_BITMAP_SIZE=>56,
:HEAP_PAGE_BITMAP_PLANES=>4,
:HEAP_PAGE_SIZE=>16384
}
これらの定数を1つずつ見ながら定義することで、Rubyがどのようにメモリを構成しているかをさらに詳しく知ることができます。
:DEBUG => FALSE
:DEBUG
という定数は、Rubyでプログラムを書くときのデバッグよりも、Rubyそのもののソースコードを書くときのデバッグに便利です。実行するほとんどのRubyプログラムは:DEBUG => FALSE
になっているので、通常気にする必要はありません。
興味しんしんの方向けに説明すると、:DEBUG
がTRUE
になるのは、Rubyのソースコードをコンパイルする際に、cppflagでDGC_DEBUG
をtrueに設定した場合のみです(参考)。しかしRubyのソースコードをコンパイルすることは通常ないので、ほとんどの場合FALSE
になります。
:RVALUE_SIZE => 40
最初の重要なポイントは、*_SIZE
で終わる定数がすべて「バイト単位」になっていることです。つまり定数:RVALUE_SIZE => 40
は、RVALUE
が40バイトに設定されていることを示します。
なるほど、ところでRVALUE
とは何かが気になってきますね。RVALUE
はRubyオブジェクトの基本的な情報を格納します。RVALUE
は、RubyオブジェクトをCで表現するさまざまなCの構造体が共用体(union)になったものです(参考)。
RVALUE
には「Rubyオブジェクトの実際の値」という情報が含まれますが、この値が40バイトの制限を超える場合は、代わりにRubyオブジェクトの値がOSのヒープ上のどこにあるかを示すポインタが含まれます。
(細かい話ですが、文字列が23文字以下の場合はRVALUES
の中に値が格納され、23文字を超える場合はOSのヒープ上に値が格納されます。詳しくはPat Shaughnessy氏のブログ記事をどうぞ)。
:HEAP_PAGE_OBJ_LIMIT => 409
お次はHEAP_PAGE_OBJ_LIMIT
です。409は、HEAP_PAGE
ごとのオブジェクトの最大数です。ところでHEAP
やPAGE
とは何なのでしょうか。
RubyのHEAP
とは、Rubyのオブジェクト空間全体、つまりメモリのことです。HEAP
にはすべてのRVALUES
が格納されます。HEAP
はいくつかのPAGE
に分割されます。ガベージコレクタで追加のメモリが必要になった場合、オブジェクトごとに新たなメモリをOSに要求することはありません。もしそうすると、上述のRVALUE_SIZE
で学んだようにメモリを40バイト単位で要求することになるので効率が非常に悪くなります。
その代わり、HEAP
がさらにメモリを必要とする場合は、まったく新しいPAGE
を要求します。1個のPAGE
には最大で409個のオブジェクトが含まれています。その理由についてはAaron Patterson氏の素晴らしいブログ記事に書かれています。この定数からもわかるように、1個のPAGE
あたりのオブジェクト(つまりRVALUE
)の個数の上限は409個に設定されています。
:HEAP_PAGE_BITMAP_SIZE => 56
HEAP
とPAGE
についてはこれで理解できましたが、今度のBITMAP
は何でしょうか。各HEAP_PAGE
には、オブジェクトのビットマップ表現も含まれています。各オブジェクトは1つのビットで表現されます。
ところで、:HEAP_PAGE_BITMAP_SIZE => 56
は、ビットマップ用にメモリを56バイト使うという意味です。ここで算数のお時間です。1バイトは8ビットなので、56バイトは56 * 8 == 448
ビット、つまり448個のオブジェクトを扱えるということなので、上の:HEAP_PAGE_OBJ_LIMIT => 409
個のオブジェクトを表現するのに十分な値です。
しかし、そもそもすべてのオブジェクトを表現するのになぜビットマップが必要なのかという疑問が残っています。ビットマップは、Rubyがガベージコレクションに使われる実際のアルゴリズム(具体的には「3色マークアンドスイープ」アルゴリズム」に「統合」されています。このアルゴリズムを詳しく説明しようとするだけで優にブログ記事1本分になるでしょう。さしあたっては、ビットマップがページごとに存在することと、ページ内の各オブジェクトを格納するのに十分なスペースが確保することが重要であるとお考えください。
:HEAP_PAGE_BITMAP_PLANES => 4
正直申し上げると、この定数は謎です。Rubyのソースコードを検索してみると値が4に設定されていることはわかったのですが、変数自体がまったく使われていないのです。この値は私の知る限り未使用です。planeをググるはずがついつい飛行機(airplane)をググって最近ご無沙汰の旅行気分にしばし浸ってしまいました。
冗談はともかく、HEAP_PAGE_BITMAP_PLANES
の意味についてご存じの方がいらっしゃったら、ぜひ教えてください。知りたくてたまりません。いにしえのRubyの名残りか何かだと勘ぐっているのですが...
訳注
著者のツイートへの返信を見つけたので貼っておきます。なお、HEAP_PAGE_BITMAP_PLANES
を削除するプルリク#4154を著者が出していますが、現時点(2021/06/04)でオープンのままです。
Thanks for sharing! I put up a PR to remove it yesterday https://t.co/yCs5yabfbR so we'll see where that goes
— Jemma Issroff (@JemmaIssroff) February 5, 2021
:HEAP_PAGE_SIZE => 16384
最後の定数はページそのもののサイズです。16384バイトということは、1ページあたり16KB強ということです。これらのページにあるのはは、若干の情報を含むヘッダーと、すべてのRVALUE
です。RVALUE
を格納する場所は「スロット(slot)」と呼ばれます。先ほど HEAP_PAGE_OBJECT_LIMIT
は409個と書きましたので、言い方を変えれば1ページあたり最大で409個のスロットがあるということになります。
再び算数のお時間です。1つのページはすべてのRVALUE
を十分格納できるはずであり、それに加えてヘッダー情報のスペースも若干使われます。1ページあたりのRVALUE
が最大409個であり、1つのRVALUE
が40バイトであることがわかったので、409 * 40 == 16,306
バイトとなって16384
バイトを下回っています。
まとめ
図で理解したい方のために、上を図で解説しました。
ご紹介した定義も手短にまとめました。
- Heap(可変サイズ)
- Rubyのオブジェクトスペース(つまりメモリ)。Rubyはページをすべてここに保存する。
- Page(〜16KB)
- Rubyによるヒープの分割単位。ヒープには複数のページが含まれる。ページ全体で最大409個のスロットが含まれる。
- Slot(40バイト)
- 1つのページ上の領域。1個の
RVALUE
がここに保存される。 - RVALUE(40バイト)
- RubyオブジェクトのC表現。オブジェクトの値が含まれることもあれば、OSヒープ上のオブジェクトを指すポインタを含むこともある。
今回の記事は以上です。初めに述べたように、今後もガベージコレクションについてブログ記事と本の両方でいろいろ書いていく予定です。新着記事を見逃したくない方は、原文末尾のフォームでメールアドレスをお知らせいただければ新着記事を通知いたします。
概要
原著者の許諾を得て翻訳・公開いたします。