メモリアロケーションとコンテキストスイッチの関係

OCamlコンテキストスイッチのタイミングを調べた。

OCaml では OSネイティブのスレッド (pthread) を使える。 しかし、OCamlのランタイム内で同時に走るネイティブスレッドは1本だけ、という制限がある。OCamlはスレッドセーフなGCを備えていないためだ。 スレッドはプログラムを構造化して書くために使うツールと割り切ろう、ということらしい。 OCamlの(シングルスレッドの)高いパフォーマンスの代償だろうか。
いずれコンカレントGCはサポートされるだろうと期待するけれど*1、待ってられないので現在の話を考えよう。

動機 : スレッドのコンテキストスイッチの頻度

OCamlで書いたバイナリとCやJavaを協調動作させるとき、この1スレッド制限がどう効いてくるのかすこし気になっている.たとえば 1つのスレッドが OCaml側で走っているときに C to OCaml のコールバックで 別のスレッドがOCaml側にやってきた場合に、どの程度待たされるのだろうか? 例えばGUIでは普通にそういうことは起こっているだろう.
OCamlでは,コンテキストスイッチはどの頻度で起こるのだろうか? OCaml 3.11.1 のソースを追ってしらべてみた.
以下の話は ネイティブ(ocamloptでコンパイルしたコード)で走っているコードを仮定している.

OSによるプリエンプションは不都合

普通,スレッドは同期命令やI/Oやシステムコールのタイミングでコンテキストスイッチされる.これらを呼ばず CPUを占有していると、OSによってプリエンプションされることになる.
しかしたとえばGCやってる途中とかのタイミングでプリエンプトされては困る(だろう,多分).OCamlはどうしているのだろう?

tickスレッドとSIGVTALRM

OCamlのプロセスは起動直後 1本のスレッドで動いているが、別のスレッドを起こすと、補助的な もう一本の tickスレッドが走りはじめる.このtickスレッドは50msecおきに UNIXのシグナルSIGVTALRMっぽいものを出す.(っぽいものって何?と思った人はソース (otherlibs/systhreads/posix.c) を読んで.)
SIGVTALRMシグナル(モドキ)は,OCamlのスレッドの切り替えを指示するシグナルだ.

実行(中|可能)なのは高々2本のスレッドだけ / プリエンプションで起こること

マルチスレッドで実行中のOCamlプロセスは、この tickスレッドともう1本のスレッドのみが実行可能(もしくは実行中)であり, 他のスレッドは条件変数で待ち状態である.OSによるプリエンプションは tickスレッドと実行可能スレッドの2者間を切り替える. 他のスレッドは、SIGVTALRM(モドキ)のハンドラによって条件変数が叩かれてウェイクアップされるまではずっと寝ている.

メモリ確保のタイミングでコンテキストスイッチへ…

SIGVTALRM(モドキ)とかのUNIXシグナルはただちに処理されずフラグが立てられる.
この「UNIXシグナルが来たよフラグ」はメモリ確保とGCのタイミングで(事前にSys.signalでインストールした)ハンドラにより処理される.
SIGVTALRMのハンドラは Thread.yield に設定されているので, 眠っていたスレッドに対する コンテキストスイッチが 見事 このタイミングで 発生する.目出度い.

テスト

このコード は、 forever関数と wait関数をそれぞれ並行に動かす. 関数のなかで allocate というメモリ確保を行う関数を呼び出した場合には コンテキストスイッチが起こり,waitスレッドは終了できる. 一方, allocation関数を外した場合にはコンテキストスイッチが発生せずwait関数が終了しない.

他の話 : コンテキストスイッチを引き起こすメモリ確保とそうでないメモリ確保

コンテキストスイッチOCaml側のメモリ確保に付随するGCでのみ発生する.例えば,強制的にGCするGC.minorやGC.majorではスイッチしない.また,上の例の allocation2 のように, 標準ライブラリの関数でメモリを確保してもスイッチしない.

まとめ

OCaml 3.11.1/ocamloptでのコンテキストスイッチ

に起こる.より古いバージョンでもある程度までは同じだろう.

素人の疑問

  • 割り込みマスクみたいな、プリエンプションを禁止するような命令があればそこでGCすればいいんじゃねー
    • そんな機能はユーザレベルで存在するのか
    • プライオリティを最高にしたらどうか
  • なぜ 直接 シグナルを処理しないのか?
    • なぜ SIGVTALRM をsetitimerせずスレッドからたたくのか?
    • なぜ SIGVTALRM は生シグナルではなく単なるフラグになっている(posix.c)のか?
      • 予想1. tickスレッドのオーバーヘッドを見越してもその方が速い
      • 予想2. 単に互換性の理由
  • 比較 : ruby も SIGVTALRM でスケジューリング http://www.loveruby.net/ja/rhg/book/thread.html
    • この分だと他にもありそう

GCをある程度しらないと辛いなあ

*1:これについてコメント欄参照