CTC 教育サービス
[IT研修]注目キーワード Python UiPath(RPA) 最新技術動向 Microsoft Azure Docker Kubernetes
昨年末に上梓された「Rubyのしくみ」(原題:Ruby Under a Microscope, 顕微鏡の下でのRuby)という本を手にしました。Rubyのしくみを知りたい、Rubyを深く知りたいという欲求からの行為です。
そこで今回はRubyの内部構造 "Ruby Internals" の理解の助けになればという願いを込めて少しでも深層に近づくために潜ってみたいと思います。ちなみに筆者は金槌です。いつも謎に満ちているRubyのオブジェクトとクラスについて、その素顔を垣間見ることができれば嬉しいという試みです。
オープンソースとして公開されているRuby実行環境であるインタープリター(interpreter)の代表は、まつもとゆきひろ氏によって開発された MRI(Matz' Ruby Implementation)です。MRIはC言語で実装されたRubyの公式処理系であり、すべてのプラットフォームで動作可能として提供されています。MRIは最も広く使われているRubyのインタープリターです(MRI以外にもRubyはオープンソースであるが故に多数の処理系が実装されています)。
先ずは安全第一に救命胴衣を着用して(「Rubyのしくみ」を片手に携えて)、MRIのソースコード(Rubyバージョン2.2.2)をダウンロードしてみました。
ソースコード海の中に浮遊するヘッダーファイルを覗くとその底にはRubyのオブジェクトが、RObjectという構造体で表現されていました。
struct RObject { struct RBasic basic; union { struct { long numiv; VALUE *ivptr; struct st_table *iv_index_tbl; } heap; VALUE ary[ROBJECT_EMBED_LEN_MAX]; } as; };
RObjectがRubyの汎用のオブジェクトとして用意されている構造体です。
unionで共用体として指定されているその内部にある構造体がインスタンスの値を指し示している様子です。numiv(number of instance variables)がインスタンス変数の「個数」、ivptr(instance variables pointer)にはそのオブジェクトのインスタンス変数の「値」が格納された配列へのポインタが保存される様子です。そしてiv_index_tblは、後述するRClassと連動しており、個々のインスタンス変数の「変数名とインデックス」が対応付けられたハッシュテーブルへのポインタとなっているそうです。(因みにデータ型の st_table がC言語でのハッシュテーブル実装であり、Rubyでもハッシュの基礎となっているのでiv_index_tblは本質的にハッシュだそうです)つまり、RObjectが保持するのはそのオブジェクトが固有に保持するインスタンス変数の値であり、変数名というインスタンス変数そのものの情報はクラスにあることになります。インスタンス変数は、クラスで定義しているのですから納得できる構造です。
ここでRObjectで冒頭に記載されているメンバーが、RBasicとなっています。RBasic構造体を探すと以下のようになっていました。
struct RBasic { VALUE flags; const VALUE klass; }
flagsにはオブジェクトの状態を示す frozen, tainted などの情報がフラグとして格納されます。もう一つのメンバーであるklassはクラスポインタを表現しており、現在のオブジェクトのクラスを指すのです。オブジェクト共通に必要な情報がRBasic構造体なのです。逆説的には、すべてのインスタンスはRBasic構造体を含むのだと考えて概ね良さそうです。ここでklassが指している先にあるのが、クラスを表現しているRClass構造体です。
struct RClass { struct RBasic basic; VALUE super; rb_classext_t *ptr; struct method_table_wrapper *m_tbl_wrapper; };
RObjectと同様にRBasicをメンバーに持っていることから、クラスもオブジェクトであることが分かります。それに加えてRClassは、superポインタをメンバーに持っており、これにより継承元であるスーパークラスを指し示しているのでしょう。またメンバーであるptrポインタはクラス固有の情報を持っているようです。ptrの型であるrb_classext_tはtypedefされており、rb_classext_struct構造体を意味しています。
struct rb_classext_struct { struct st_table *iv_index_tbl; struct st_table *iv_tbl; struct st_table *const_tbl; rb_subclass_entry_t *subclasses; rb_subclass_entry_t **parent_subclasses; rb_subclass_entry_t **module_subclasses; rb_serial_t class_serial; VALUE origin; VALUE refined_class; rb_alloc_func_t allocator; };
同様にmethod_table_wrapperも構造体です。
struct method_table_wrapper { st_table *tbl; size_t serial; };
それぞれクラス固有の情報とインスタンスメソッドの定義を指していると考えられます。ここまでで分かったことは、すべてのRubyオブジェクトは、クラスへのポインタとインスタンス変数の組み合わせだということになります。これがRubyオブジェクトの定義なのです。例えば、下記のようなRubyのサンプルコードで考えてみます。
class Deepsea attr_accessor :depth attr_accessor :latitude attr_accessor :longitude attr_accessor :date end maria = Deepsea.new maria.depth = 5815 maria.latitude = 25.31 maria.longitude = 143.275 maria.date = "1992/09/21" p maria
ここでインスタンスとして生成された変数名mariaが参照する実体はRObjectであり、このmariaを生成する基となったDeepseaクラスはRClassが実体となるはずです。そして変数名mariaとして参照されるRObjectに内包しているRBasicのメンバーであるklassポインタがDeepseaクラスであるRClassを指しているという構図と理解できます。
そしてこの時に変数名mariaとして参照されるRObjectのメンバーであるnumivにはインスタンス変数の合計数として「4」という数値が入っており、そしてivptrポインタはこのオブジェクトのインスタンス変数の値となる要素を四つ持っている配列を参照していることになります。
この水深でもかなり息苦しくて金槌の筆者は溺れそうになってきましたが、ついでにもう少しだけ潜ってみることにします。
ここまでのソースコードでivptrやklassなどの各所でポインタの型として指定されているのがVALUEですが、このVALUE型を探してみるとtypedefされていました。
typedef uintptr_t VALUE;
もしくは、
typedef unsigned long VALUE;
内部的にRubyは何等かの値への参照は、すべてVALUEポインタを用いるのだそうです。先ほど例示したRubyのコードでは、変数mariaの本性がまさにVALUEポインタであります。つまり、ユーザ定義のインスタンスを生成してそのオブジェクトを指し示す際の変数、まさにこの変数の実体がVALUEポインタであり、インスタンスであるRObjectを指しているのです。
VALUEについてはもう一つ大事な情報があります。
Rubyの世界ではすべての値がオブジェクトとして用意されることで、プログラム中で透過的に扱えることがとても有効なのですが、組み込みタイプの基本データ型である整数や文字列、浮動小数点数、シンボルなどのオブジェクトも実体はRObjectであると考えるのが妥当ですが、どうやら違うのだそうです。
基本データ型は汎用的なRObjectではなく、異なる構造体を用いるのだそうです。例えば、文字列だと RString構造体が用意されています。
struct RString { struct RBasic basic; union { struct { long len; char *ptr; union { long capa; VALUE shared; } aux; } heap; char ary[RSTRING_EMBED_LEN_MAX + 1]; } as; };
そして浮動小数点数にはRFloat構造体がありました。
struct RFloat { struct RBasic basic; double float_value; };
同様に配列にはRArray、正規表現にはRRegexp構造体がそれぞれ用意されますし、他にもハッシュやIO、File、Socketなども専用の構造体が用いられます。これらはユーザ定義クラスとは異なり、基本データ型として特定の情報を保持できるように最適化されていると考えることができます。但し、RStringもRObjectと同じくRBasicが含まれていることから、ちゃんと特定のクラスであることを示すことができますのでRubyオブジェクトとして同等に扱える仕組みとなっていることが理解できます。
加えて、小さな整数(Fixnum)やシンボル(Symbol)のオブジェクトに至っては更なる最適化が為されていました。VALUEポインタ自体に値を保持しているのです。
前述の様にVALUEは数値型ですから、そのままVALUEに整数値を保持しているのです。そして下位ビットをフラグとして使うことで、どのオブジェクトかを表現するのです。Fixnumの場合には、最下位ビットを「1」とすることで数値のクラスだと分かるのです。この様にすることで余計な構造体を使わずに値そのものをVALUEに埋め込み、その上にオブジェクトであることも表現しています。これにより最適化に大きく貢献できるという工夫がされているのです。
列挙型として定義されているフラグの一覧を見つけました。
enum ruby_special_consts { #if USE_FLONUM RUBY_Qfalse = 0x00, /* ...0000 0000 */ RUBY_Qtrue = 0x14, /* ...0001 0100 */ RUBY_Qnil = 0x08, /* ...0000 1000 */ RUBY_Qundef = 0x34, /* ...0011 0100 */ RUBY_IMMEDIATE_MASK = 0x07, RUBY_FIXNUM_FLAG = 0x01, /* ...xxxx xxx1 */ RUBY_FLONUM_MASK = 0x03, RUBY_FLONUM_FLAG = 0x02, /* ...xxxx xx10 */ RUBY_SYMBOL_FLAG = 0x0c, /* ...0000 1100 */ #else (省略) };
整数(Fixnum)のみならず、シンボル(Symbol)やtrue, false, nilなどの擬似変数までもが同じくフラグで表現されていることが伺い知れましょう。
ここまでの海底散策では、Rubyではオブジェクトの実体を構造体など(RObject,RString, ... )で表現し、扱うときは常にVALUEポインタ経由で扱います。構造体はクラスによっては違う型を使いますが、ポインタはどの構造体を指し示す場合でも常にVALUE型を使います。そして時にはVALUE型がポインタではなく値そのものである事までもが分かりました。
今回は深淵なるRubyの海の底に向かって、ちょっとだけ深く潜ってみました。続きの機会があれば、また潜ってみたいと思います。くどいですが筆者は金槌なのです。それでも、深い海の底で聖母に逢えたような気がしたので「深海マリア」と題させて頂きました。ご本尊のほうが、もっとしっくりくるかもしれません。
タイトルとした「深海マリア」ですが、日本のバンドである "VANIRU" の 1st アルバム 「MASQUERADE OF COSMIC」(マスカレード・オブ・コズミック)に収録されている楽曲名でもあります。今年3月に発売された2nd シングル「LOVE AGAIN」にも「深海マリア」の別バージョンがカップリングとして収録されています。最近のヘビーローテーションです。
そして以前のニュースなのですが、日本列島から南方百キロの海底にある小笠原海台で日本の潜水調査船「しんかい6500」が奇妙なものを見つけたのが話題になりました。その奇妙なものとは、水深約五千八百メートルでみつけた珊瑚礁が変成した巨大な石灰岩ですが、まるで「やさしく微笑む聖母マリア像のようだ」と見つけた研究者は思われたそうです。まさしく「深海マリア」です。ご興味あればですが、海洋研究開発機構(JAMSTEC)のホームページで「しんかい6500」の撮影した映像が公開されており、このマリア像をご覧になれます。
この奇蹟のマリア像も数万年後には消えてしまう運命だそうです。しかしながら、数万年という膨大な時間軸を前に置かれてしまいますと、見られる対象よりも見る側そのものが一瞬で消え去ってしまうのではと考えてしまい、何を以て儚いと云えるのかが微妙ではないのかと拙い妄想に駆られてしまいます。
次回もお楽しみに。
[IT研修]注目キーワード Python UiPath(RPA) 最新技術動向 Microsoft Azure Docker Kubernetes