Embed Ruby

: subtitle

アプリケーションへの\n
Rubyインタープリターの組み込み

: author

須藤功平

: institution

株式会社クリアコード

: content-source

東京Ruby会議11

: date

2016-05-28

: allotted-time

25m

: theme

clear-code

Speaker’s award

Continuous development award

受賞者

((‘tag:center’)) ((‘tag:x-large’)) cedlemo

((‘tag:center’)) ((‘note:Continuous development award’))

受賞理由

((‘tag:center’)) ((‘tag:large’)) 2015年1月から継続的にn Ruby-GNOME2の開発にn 参加しているから

((‘tag:center’)) ((‘note:一発すごい改善をした人よりも’))n ((‘note:地味でも継続的に改善している人を評価したい’))

((‘tag:center’)) ((‘note:Continuous development award’))n ((‘note:cedlemo’))

宣伝

OSS Gate

OSS Gate

OSS開発にn 参加する人をn 増やす取り組み

背景

* OSS利用は当たり前になった
  * →OSS利用者増加
* (('wait'))開発参加者も増えるといいな
  * →OSS増加

((‘tag:center’)) ((‘note:OSS Gate’))

OSS開発参加

* すごい改善じゃなくていい
* (('wait'))バグレポートとかでいい
  * typo見つけました!とかでもいい
  * サンプルを更新とかでもいい

((‘tag:center’)) ((‘note:OSS Gate’))

OSS Gate参加の動機

((‘tag:center’)) ((‘tag:large’)) 人それぞれでいい

((‘tag:center’)) ((‘note:OSS Gate’))

私の動機

* ユーザーが自由に使える\n
  ソフトウェアが増えるといいな
* 自由に使える例:
  * (('wait'))コードを読んで学習できる
  * (('wait'))今日聞いた話の実装を確認できる!

((‘tag:center’)) ((‘note:OSS Gate’))

興味ある?

* 興味?(重要:動機不問)
  * OSS開発に参加したい!
  * OSS開発参加者を増やしたい!
* (('wait'))5階でワークショップ開催中
  * 説明や見学は私に一声かけて

((‘tag:center’)) ((‘note:OSS Gate’))

本題

Rubyの組み込みn ((‘note:(CアプリケーションへのRubyインタープリターの組み込み)’))

動機

* 柔軟な記述力が欲しい
* (('wait'))Cの速さが欲しい
* (('wait'))Ruby以外の言語とも連携したい
  * Pythonも組み込む
* (('wait'))なんかカッコいい

((‘tag:center’)) ((‘note:Rubyの組み込み’))

別の実現方法

((‘tag:center’)) ((‘tag:large’)) 拡張ライブラリー

((‘tag:center’)) ((‘note:Rubyの組み込み’))

組み込み((‘note:と’))拡張ライブラリー

# image
# src = images/embed-and-extension.svg
# relative_width = 90

((‘tag:center’)) ((‘note:Rubyの組み込み’))

拡張ライブラリー

* 実現可能:
  * (('wait'))柔軟な記述力が欲しい
  * (('wait'))Cの速さが欲しい
* 実現不可能:
  * (('wait'))Ruby以外の言語とも連携したい
  * (('wait'))なんかカッコいい感

((‘tag:center’)) ((‘note:Rubyの組み込み’))

実現方法の選び方

* (('wait'))基本は拡張ライブラリー
* (('wait'))すでにあるアプリなら組み込み
* (('wait'))選びたい方があるならそっち

((‘tag:center’)) ((‘note:Rubyの組み込み’))

組み込みを選ぶ時の注意

((‘tag:center’)) ((‘wait’))それなりの覚悟が必要

* (('wait'))利用例があまりない
  * 問題遭遇確率が高い
* (('wait'))問題遭遇時:
  * 自分でソースを読んで調べる
  * 詳しい人に相談\n
    (('note:ささださん<できればサポートを強化したい'))

((‘tag:center’)) ((‘note:Rubyの組み込み’))

組み込み方を紹介

実例n milter managern ((‘note:Since 2008’))

milter manager

# image
# src = images/milter-manager-overview.svg
# relative_width = 85

((‘wait’)) ((‘tag:center’)) milterを管理するmiltern ((‘note:サーバープロセス’))

Ruby組み込みの実装

* 初期化
* fork対応
* イベントループとシグナル

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

初期化:GC関連

# coderay c
{
  /* スタックの底を設定 */
  /* GC時にCのローカル変数に代入されている
     Rubyのオブジェクトをマークするため */
  RUBY_INIT_STACK;
  /* ... */
}

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

スタックとマーク対象

# coderay c
{
  RUBY_INIT_STACK;
  /* ... */
  {
    VALUE object = rb_ary_new(); /* マーク対象 */
  }
}
{
  VALUE object = rb_ary_new(); /* マーク対象外 */
}

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

確認例

# coderay c
#define MARKED_P(object) rb_objspace_marked_object_p(object)
{
  RUBY_INIT_STACK;
  {
    VALUE object = rb_ary_new();
    /* GC.start(immediate_sweep: false) */
    printf("%d\n", MARKED_P(object)); /* => 1 */
  }
}
{
  VALUE object = rb_ary_new();
  /* GC.start(immediate_sweep: false) */
  printf("%d\n", MARKED_P(object));   /* => 0 */
}

((‘tag:center’)) ((‘note:スライドのリポジトリー:examples/gc.c’))

GC関連の注意

# coderay c
{
  RUBY_INIT_STACK;
  /* Rubyのオブジェクトを触る
     Cのコードはこのブロック内でだけ使うこと */
  /* Cからのコールバックで
     Rubyのコードを呼び出すときは注意 */
}

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

初期化:シグナル関連

# coderay c
{
  RUBY_INIT_STACK;
  /* シグナルハンドラーを保存 */
  ruby_init(); /* Rubyがシグナルハンドラーを登録 */
  /* シグナルハンドラーを復帰 */
  /* シグナルはアプリで処理したいから */
}

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

シグナル復帰例

# coderay c
{
  /* 他のシグナルも同様に復帰 */
  void (*sigint_handler)(int);
  sigint_handler = signal(SIGINT, SIG_DFL);
  ruby_init();
  signal(SIGINT, sigint_handler);
}

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

初期化:引数の処理

# coderay c
{
  /* ...ruby_init()... */
  static char *argv_raw[] = {"milter-manager", "-e;"};
  int argc;
  char **argv;
  argc = sizeof(argv_raw) / sizeof(char *);
  argv = argv_raw;
  ruby_incpush(/* ... */); /* $LOAD_PATHの設定 */
  /* 中でいろいろ初期化するのでダミーの引数で呼ぶ */
  ruby_process_options(argc, argv);
}

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

初期化:アプリの初期化

# coderay c
{
  /* ...ruby_process_options()... */
  /* require中に例外が発生してもここで止める */
  /* ここで止めないと例外を受け取る人がいなくて
     クラッシュ */
  rb_protect(/* rb_require("milter/manager") */);
}

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

milter managerとRuby

* 組み込み処理系の1つ
  * Pythonも使えるようにしたかった\n
    (('note:結局Ruby必須でPython対応はしなかった'))
* (('wait'))起動後に(({dlopen()}))で動的にsoを読み込んで組み込み

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

起動時に動的に組み込み

# image
# src = images/milter-manager-and-ruby.svg
# relative_width = 90

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

起動時にso読んで組み込み

# coderay c
{
  /* ↓GC用 */
  RUBY_INIT_STACK;
  /* 動的にsoを読んで初期化関数を呼ぶ */
  /* dlopen();
     init = dlsym();
     init(); ←の中でruby_init();とか */
  /* アプリの処理 */
}

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

RUBY_INIT_STACK!?

# coderay c
{
  /* ↓アプリ側で呼ぶの!? */
  RUBY_INIT_STACK;
  /* 動的にsoを読んで初期化関数を呼ぶ */
  /* dlopen();
     init = dlsym();
     init(); ←の中でruby_init();とか */
  /* アプリの処理 */
}

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

アプリにーlruby…

# image
# src = images/milter-manager-and-ruby-real.svg
# relative_width = 90

((‘tag:center’)) ((‘note:カッコわるい。。。’))n ((‘note:milter managerへのRubyの組み込み’))

Ruby組み込み時の意気込み

* 本体に組み込む
* 動的に組み込もうとしない

Ruby組み込みの実装

* (('del:初期化'))
* fork対応
* イベントループとシグナル

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

milter manager利用例

* (('wait'))大学・企業
  * ユーザー数:数百〜数万人
* (('wait'))プロバイダー
  * ユーザー数:数千〜数十万人

((‘wait’)) ((‘tag:center’)) それなりの性能が必要

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

性能向上方法

* CPU
  * マルチプロセス1択
* 通信・多同時接続
  * いろいろ

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

マルチプロセス

* (('wait'))マスタープロセス
  (1) (({listen()}))
  (2) (({fork()}))
* (('wait'))ワーカープロセス
  (1) (({accept()}))
  (2) ↑したクライアントの処理

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

Ruby組み込みと(({fork()}))

* (({fork()}))すると\n
  ワーカープロセスがクラッシュ
  * プロセス終了時とか
* (('wait'))ヒント:(({fork()}))とスレッド

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

(({fork()}))とスレッド

* 混ぜるな危険
* (('wait'))Rubyはスレッドを動かしている
  * 例:タイマースレッド
* (('wait'))(({fork}))時にスレッドのケアが必要

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

スレッドのケア

# coderay c
VALUE rb_pid;
/* タイマースレッドの後始末とか
   した上でfork */
rb_pid = rb_funcall(rb_mKernel,
                    rb_intern("fork"),
                    0);
return NUM2INT(rb_pid);

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

Ruby組み込みの実装

* (('del:初期化'))
* (('del:fork対応'))
* イベントループとシグナル

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

イベントループとシグナル

* 気にしなくてよい
  * (('wait'))アプリがシグナルを処理するから
* (('wait'))拡張ライブラリーなら対応必要
  * イベントループ中にシグナル発生
  * →すぐにイベントループを抜ける

((‘tag:center’)) ((‘note:milter managerへのRubyの組み込み’))

Rubyの組み込みのまとめ

* Rubyを組み込む実装方法を紹介
* (('wait'))動的組み込みは諦めろ
* (('wait'))(({fork}))時はRubyの(({fork}))を使う

((‘tag:center’)) ((‘note:Rubyの組み込み’))

mrubyの組み込み

実例n Groongan ((‘note:Since 2013’))

Groongaとmruby

* Groonga
  * 全文検索エンジン(mruby組み込み)
  * (('wait'))高速に検索結果を返し続けたい
  * (('wait'))リソース消費は波がない方がよい\n
    (('note:例:いらなくなったメモリーはすぐに解放'))

((‘tag:center’)) ((‘note:mrubyの組み込み’))

リソース消費

# image
# src = images/groonga-memory-usage.svg
# relative_height = 90

((‘tag:center’)) ((‘note:mrubyの組み込み’))

メモリー管理

* Groonga
  * (('wait'))必要なときに確保
  * (('wait'))いらなくなったら解放
* mruby
  * (('wait'))GC
  * (('wait'))メモリーが足りなくなったら解放

((‘tag:center’)) ((‘note:mrubyの組み込み’))

GroongaとmrubyのGC

* mrubyのGCにGroongaのリソース管理を任せない
  * リソース管理:\n
    mrubyのオブジェクトのsweep時にGroongaのリソースを解放

((‘tag:center’)) ((‘note:mrubyの組み込み’))

mrubyのGCとリソース

* mrubyのGC
  * Groongaリソースのサイズを知らない
  * 適切なタイミングでsweepできない
  * (('wait'))(('note:RubyのGCも同じ'))

((‘tag:center’)) ((‘note:mrubyの組み込み’))

実例1: 明示的な解放

# coderay ruby

# 検索
result = table.search(condition)
begin
  output_result(result) # 出力
ensure
  result.close # 明示的な解放
end

((‘tag:center’)) ((‘note:mrubyの組み込み’))

実例2: 所有権を渡さない

# coderay c

/* Groonga側でリソース確保 */
expr = grn_expr_create(/* ... */);
/* mrubyのオブジェクトとしてラップ */
mrb_expr = grn_mrb_value_from_grn_obj(mrb, expr);
/* mruby側は参照して処理する */
mrb_size = mrb_funcall(mrb, mrb_expr,
                       "estimate_size", 1, mrb_table);
/* Groonga側でリソース解放 */
grn_expr_close(expr);

((‘tag:center’)) ((‘note:mrubyの組み込み’))

mruby組み込みのまとめ

* GCに任せないという選択
  * 使用メモリー量を安定させるため
  * 安定した性能を出すため

((‘tag:center’)) ((‘note:mrubyの組み込み’))

まとめ

* Rubyの組み込み
  * (('wait'))ガッツリ連携するつもりで設計\n
    (('note:拡張ライブラリーで十分じゃないかよく検討すること'))
* mrubyの組み込み
  * (('wait'))アプリの大事な事を忘れないで設計
* (('wait'))OSS Gateもよろしく