この投稿はmruby Advent Calendar 2013の24日目の記事です。
23日目はyamanekko さんのmrubyをRaspberry Pi(bare metal)で動かす: Windows編、25日目はyukihiro_matzさんの24時間一人mrubyハッカソンです。
はじめに
mrubyはカスタム性の高さも売りの一つで、/include/mrbconf.hを弄ることである程度動作を変えられるようになっています。
しかし、設定の多さに圧倒され、すべてデフォルトのまま使っているという人も多いのではないでしょうか?
そこで今回はわかる範囲で各種設定について説明してみようかと思います。
mrubyの設定(特にメモリーレイアウトに関する部分)は環境により動作が異なることが多々あります。
今回は筆者の開発環境であるVisualStudio2013にて試したものであり、gccやclangや特定環境専用のコンパイラなどでは結果が異なる可能性があることを念頭に置いてお読みください。
2013/11/25当時(git hash: f5bd87b9e6d0d8a84cf866b4847c1416e4f5c622 )の物です。
それ以降のmrubyを使用する場合は以下の解説の通りではない可能性があります。
2013/12/24 23:30追記
MRB_GC_TURN_OFF_GENERATIONALとMRB_GC_FIXED_ARENAの説明が間違っていたため、関連記述を変更しました。
また、間違いの指摘をしてくださったMatzさん、どうもありがとうございます。
MRB_USE_FLOAT
実数を扱う型をdoubleからfloatに変更します。
VS2013でデフォルト設定であればmrb_valueのサイズは16byteですが、これを定義すると8byteになります。
しかし当然ですがfloatになるので精度がかなり落ちます。
実数なんか使わないからメモリー消費量抑えたい場合などに使うといいでしょう。
MRB_INT16
整数を扱う型をint32_tからint16_tに変更します。
しかし、ポインターなどを扱う都合からmrb_valueのサイズが縮まったりはしません。
おそらくは16bit CPUなど2byte演算が常用される環境用なのだと思われます。
MRB_INT64
整数を扱う型をint32_tからint64_tに変更します。
MRB_INT16と同時には使えません。
デフォルトの設定(MRB_USE_FLOATやMRB_XX_BOXINGが無効の環境)では特にmrb_valueのサイズが増えたりはしないので、32bit範囲を超える演算を多用するなら有効にするのもよいでしょう。
MRB_NAN_BOXING
実数の特殊状態であるNaNの定義の一部を利用して値を投入することでメモリー消費量を落とす機能を有効にします。
MRB_USE_FLOAT、MRB_INT64とは同時には使えません。
一般的な環境であればこれが有効になるとmrb_valueのサイズが16byteから8byteになります。
ただし実数演算の実装依存なため、環境により動かない可能性があります。
MRB_ENDIAN_BIG
一部構造体のメンバーの配置順を変更します。
名前から誤解されがちですが、整数を格納するエンディアンがビッグエンディアンになったりはしません。
メンバーのアドレスが変わるぐらいしか変化がないので、通常の環境であればON/OFFどちらでも特に影響はない設定です。
MRB_WORD_BOXING
ポインターは下位2bitは使われないことと、整数の1bitを犠牲にすることでメモリー消費量を落とす機能を有効にします。
MRB_NAN_BOXINGと同時には使えません。
mrb_valueは4byteになりますが実数がポインターで保持されるため、実数を多用すると逆にメモリー消費量が増えてしまいます。
VS2013ではバグなのか私が知らない仕様なのか、MRB_INT16かMRB_INT64と共に定義すると一部ビットフィールドが無視されサイズが増えてしまいます。
MRB_FUNCALL_ARGC_MAX
mrb_funcall関数で渡せる引数の最大数を設定できます。
mrb_funcall関数はここで設定した分の長さを持つmrb_valueの配列をスタックに確保するので、スタックに余裕がない環境の場合は小さく設定するとよいでしょう。
(といっても配列一つなので大して変わりませんが)
ちなみに、ここで設定された以上の引数を要求するメソッドもmrb_funcall_argvを使えば呼ぶことができます。
なので、mrbgemなどどういう設定で使用されるかわからないものを実装する場合、mrb_funcall_argvを使ったほうがいいかもしれません。
MRB_HEAP_PAGE_SIZE
インスタンスなどに使用するヒープ領域の1確保単位を設定できます。
1確保単位と書きましたが、単位はbyteではなくインスタンスで、1024なら新たなヒープを確保せずに1024インスタンスを割り当てることができます。
またヒープを使用するのはあくまでインスタンスそのものだけで、arrayの本体である動的確保部分などは別途mrb_mallocで確保されます。
MRB_USE_IV_SEGLIST
インスタンス変数を保持するデータ構造をハッシュからリンクリスト形式に変更します。
インスタンス変数が少なければハッシュ計算が不要な分高速になるかもしれません。
ちなみにインスタンス変数一つにつき1要素ではなく、(執筆時点では)4つのインスタンス変数を保持できる配列が1要素のリストになります。
MRB_IVHASH_INIT_SIZE
インスタンス変数を保持するハッシュテーブルの初期サイズを設定します。
MRB_USE_IV_SEGLISTが定義されている場合は無視されます。
MRB_IREP_ARRAY_INIT_SIZE
何に使われているか不明です。
というより、grepしても出てこなかったので今はもう使われていないのではないかと思われます。
2013/12/24 23:30追記
hash: 9fab01caでmruby本体から削除されたようです。
MRB_GC_TURN_OFF_GENERATIONAL
世代別GCを無効にします(有効ではなく無効な点に注意)。
定義すると単なる3色インクリメンタルマーク&スイープになります。
逆にコメントアウトすると、追加で古いオブジェクトは暗黙でマークしてGCする処理が加わるようです。
GCの性能比較はよくわからないので割愛。
KHASH_DEFAULT_SIZE
Hashのデフォルトの初期サイズを設定します。
Hashクラスだけではなくmruby内部で使われるハッシュテーブル(名前空間の解決用など)にも適用されるようです。
POOL_ALIGNMENT
一括解放や確保できるメモリー空間として使用される独自のメモリープールにおいて、メモリー確保時に先頭アドレスをアラインするサイズを設定します。
メモリープールは主にパース結果など内部表現を保持するために使用されています。
アラインされないと速度が低下したり、そもそも読み書きに失敗するような環境において調整することを目的としているようです。
POOL_PAGE_SIZE
メモリープールの1確保単位を設定します。
メモリープールの容量が不足するたび、このサイズ分新たに確保して動作します。
当然ですが、大きくすれば処理速度が上がり、小さくすればメモリー効率が上がります。
MRB_STR_BUF_MIN_SIZE
文字列構築用バッファ確保(具体的にはmrb_str_buf_new関数)でメモリーを確保する際の最小サイズを設定します。
mrubyで扱う文字列全般というわけではなく、あくまでCレイヤーでなんらかの文字列を構築する際のバッファにだけ影響する様子。
メモリー再確保のコストを減らすためか、設定値以下のサイズを取得しようとすると自動的に設定値まで広げて確保されるようです。
mruby本体に限ればすべての使用箇所で適切にバッファサイズを指定しているので、いっそ小さな値にするとメモリー効率は上がるかもしれません。
(大して使用されていないので微々たるものでしょうが)
MRB_GC_ARENA_SIZE
Cレイヤーで処理する際の一時オブジェクト保存域であるarenaの初期サイズを指定します。
あくまで初期サイズであり、溢れるたびに1.5倍されていきます。
よほど特殊な例を除き、ちゃんとした実装であれば100を溢れることは滅多にありません。
自身のコード由来で大きくすることを検討している場合、下記のURLを参照のうえよく考えてから行うようにしてください。
arenaの詳細は http://www.rubyist.net/~matz/20130731.html を参照のこと。
MRB_GC_FIXED_ARENA
arenaのサイズを固定にします。
その場合のサイズはMRB_GC_ARENA_SIZEになります。
固定のメリットは若干の処理速度の向上と、arenaを大量に使うコードでクラッシュするため早期発見ができることです。
省メモリーが必須な環境なら、事前に固定に変更したうえで負荷テストなどを行うとarena溢れを防止できて効率的かもしれません。
DISABLE_STDIO
エラー時の出力などコンソールへの入出力を行わないようにします。
標準入出力先が存在しない環境など用と思われます。
ENABLE_DEBUG
デバッグ機能を有効にします。
有効だとmrb_stateにcode_fetch_hookというメンバーが増え、そこに関数ポインターを代入することで、1命令実行するごとにコールバックされるようになります。
当然ですがこれが有効のままだと処理速度は低下するので、有効のままリリースするのは推奨されません。
終わりに
いかがだったでしょうか。
書いてる側からすると、ヒープやプールなど独自に管理するメモリー領域が使われていたり、ON/OFFできる機能が意外と多かったりなど、知らなかったことが意外と多くて勉強になりました。
これを元にいろいろもっと気軽にmrbconfをカスタマイズするユーザーが増えてくれたら幸いです。
ではまた。