先週の土曜日から「ガベージコレクションのアルゴリズムと実装」を読み始め、今日の朝に一旦読み終えた。読み途中で書いたブログもあり、そこにも感想などが少し書いてある。
13年前の本で内容が古くなっている部分が結構あったがそれでも学ぶことは多かった。GCを含め、プログラム言語内部の仕組みなどを知ることができた。GCの基本的な知識はだいぶ身についたと思う。特に面白かった部分をいくつか取り上げようと思う。
メモリをいかに効率よく使うか
スマホ上で動作するDalvikVMで特に意識させられたがメモリを効率よく使うためのテクニックが各所にあった。
- CopyingGCとMark & SweepGCの併用
- forwardingのアドレスをオブジェクトに書き込んでしまう
- アドレスをアラインメントして下位数桁をフラグとしてつかってしまう
- スタックを積み過ぎないように探索の深さを抑える
etc…
アドレスのアラインメントテクニック
プールを4Kバイトの倍数でアドレスをアラインメントすると
- プールの先頭アドレスをマスク処理で求められる
- 下位何bitかをフラグとして使える(必ず0になるので)
というメリットがある。例えばブロックのアドレスが0xb7e65200で所属するプールのアドレスを求めたいとする。この時4Kバイトでアラインメントされているとすると0xfffff000でマスクすることでプールの先頭アドレス0xb7e65000が求められる。また、このアドレスの下位3桁は必ず0になるためそこを何かしらのフラグとして使用することができる。フラグ用に新しくメモリを割り当てる必要がないため効率的だ。
RubyのC拡張ライブラリ
C拡張ライブラリを用いることでCでRubyのライブラリを書くことができる。Rubyをそのまま書くだけでは速度が出ない場合やCのライブラリを用いたい場合に使える。
まず3つのファイルを用意する。
hello_world.c
#include "ruby.h"
VALUE hello_world(VALUE self)
{
VALUE str;
str = rb_str_new2("Hello World\n");
rb_io_write(rb_stdout, str);
return Qnil;
}
void Init_hello_world()
{
rb_define_method(rb_mKernel, "hello_world", hello_world, 0);
}
extconf.rb
require 'mkmf'
create_makefile('hello_world')
hello.rb
require './hello_world'
hello_world()
以上のファイルを用意したら以下のコマンドを実行する。Hello World
と出力されるはずだ。
ruby extconf.rb
make
ruby hello.rb
この辺りを読めばRubyのC拡張を書けるはずだ。ちなみに以上のファイルはRuby3.0.3で動作した(ちょっと古くね?)。
Visitorパターンが結構出てくる
オブジェクトに対して処理をしたいがタプル型や辞書型など型によって関数を使い分けたい。Visitorパターンはそういった場合に役立つようだ。で、GCのプログラムにはそういった状況が結構出てくる。なんかジェネリクスと親和性高そう。デザインパターンもジェネリクスも理解はあやふやなので勉強する必要がありそうだ。
ファイナライザが厄介
オブジェクトの解放直後に呼ばれるのがファイナライザだ。便利かも知れないがこれがオブジェクトにくっついてると単純にGCできなくなる。一旦保留し後でまたGCを試みるといった処理が必要になる。各実装でファイナライザの扱いについて述べられていた。
メモリの使われ方をより意識しやすくなった
メモリ上にプログラムがヒープ領域を用意して利用するというのは分かっていたが、ヒープ上に誰がどのようにオブジェクトを配置するかというのを知らなかった。Pythonのアロケーターはmallocと合わせて4層構造で、それぞれが協調しながらアロケーション戦略を作っていて面白かった。
これからどうするか
筆者の方が書いた小さな実装のGCプログラムがある。まずはこれをデバッガで追ってみて改造するなり新しく自分でGCを書くなりしようと思う。1週間くらいでできるといいかな。