Rubyのスレッド周りを追ってみた。
最も効率が良いスレッドモデルは結論出ないんじゃないかと(物理的な環境、何を実装するか、性能以外の面でメンテナの問題とか)。
Ruby1.8系
「グリーンスレッド」
Ruby1.8ではOSではなくて仮想マシン(VM)上で実装されたマルチスレッドシステムを採用した。
これを「グリーンスレッド」という。カーネルでスレッドがサポートされていなくても動作する。
つまり、OSに依存せずにマルチスレッドを実現する。
メリット
-
スレッドの起動、並列化の性能
Linux のネイティブスレッドの性能を上回る。つまり、ネイティブスレッドより低コストで起動、並列化を行える。
起動については、独自のアドレス空間を確保する必要がなく、わずかな量の仮想メモリを取得するだけ。
並列化については、カーネルレベルとユーザレベルの切り替えが必要ないことなど。 -
さまざまなOS間で移植が楽
デメリット
- マルチコアやマルチプロセッサを全くいかせない
複数のプロセッサに処理をわりあてるなど、OS標準の平行処理を利用する事ができない。PCのプロセッサは、動作周波数の引き上げが物理的な限界に近づくことによって、マルチコアやマルチプロセッサの波が訪れたがそれを生かせない
- 他スレッドのブロック
I/O処理を実行する場合、他の全てのスレッドをブロックする可能性がある。
(ただし、グリーンスレッドがI/O関連でかならずブロックされるということでなく正しく非同期IO処理を実行すれば問題ない。何かしらの原因で、ブロックするIO処理をそのまま走らせた場合にOSからは感知できないので完全にブロックされる)
- コンテキストスイッチ(というかグリーンスレッドでのスレッド切り替えは重い)
http://subtech.g.hatena.ne.jp/mala/20090920/1253447692
Ruby1.9系〜
「ネイティブスレッド」
MRIにYARVが組み込まれ、これになった。
いわゆる普通のスレッド処理。OSの実装を生かしたもの。
グリーンスレッドより、I/Oやコンテキストスイッチの処理に関して、大きく上回る性能を持つ。
#####GIL(グローバルインタプリタロック)
ただし、現Rubyのネイティブスレッド実装はGIL(または、GVL(Giant VM Lock)とされる)で処理されているので素のネイティブスレッド処理の性能をそのまま生かすことはできない。特にマルチコアとか。
GILとは、プロセス単位にスレッドセーフでないコードを他のスレッドと共有してしまうのを防ぐための排他ロック。
ただし、VM単位でがっちりと大きなロックをしてしまうので、複数の
スレッドをもつプロセスの平行性も制限してしまう。
プロセスをマルチプロセッサのマシンで実行させても、ほとんど(全く)速度は向上しない。
そのかわり、個別のデータごとのロック処理が必要ないためシングルスレッドのパフォーマンスについては良くなる。
ただ、単純なパフォーマンスについては落ちるのでこれ採用した時に下記のような議論や意見などはいろいろあったみたく、このGILをはずしたいという意見はたびたびあがるらしい。
しかし、単純にはずすと下記のような事がおこるので今にいたるという感じみたい。
- Rubyがそもそもスレッドセーフ実装じゃない
SEGVおきるよ。だし、これからもスレッドセーフな実装を続けるの大変すぎる。
C拡張ライブラリ側でスレッドセーフなら、それを呼ぶときはGILをはずして実行しても良い。
zlibではrb_thread_call_without_gvlをコールしてGVLをはずしている。
-
C拡張ライブラリのほうだって、みんなスレッドセーフとは限らない
-
速度向上
シングルスレッドなら、そもそも今のほうが速いから直してもなー。
どうせ遅いのって、ネットワークとかI/Oまわりでしょ?
など。
なので、Ruby1.9系からのスレッド周りのメリット、デメリットは以下のようになりそう
メリット
-
コンテキストスイッチが高速
-
場合によって、GILを切ってパフォーマンスを上げることもできる
http://d.ballade.jp/2013/10/ruby-ext.html -
I/O関連のシステムコールレベルなら、スレッドの並列性を担保できる(ちょっとこれあやしい)
-
GIL排除への第一歩?
デメリット
- これはネイティブスレッドでなくYARVのデメリットだが、現在の実装では Ruby VM は Giant VM lock (GVL) を有しており、同時に実行される ネイティブスレッドはほぼ一つ。
下記は参考
なんでGILつかうの?
http://qa.atmarkit.co.jp/q/2814
-
C拡張ライブラリ(RubyからC実装で呼べるライブラリが今後もスレッドセーフと限らんし、そうだとしてもたぶんバグるだろう
-
速度向上(全てのデータ構造に個別にロックを持つ必要がなくなる)
-
同時に実行されるネイティブスレッドとしては1つ
-
ただし、ビジースレッド状態になってもタイマー管理でスレッドは切り替えられる(rubyプロセスに対して、timerのスレッドが1つある。なので、起動したrubyプロセスは なぜか2スレッドだったり)
http://d.hatena.ne.jp/kyab/20140215/1392485665 -
なんのためのネイティブスレッドなのよ
I/O処理についてはちゃんと並列でやるよと(Parallelとかで
http://lab.sonicmoov.com/development/ruby-parallel-processing/ -
Ruby のスレッド実装の改善
http://www.atdot.net/~ko1/activities/prosym2011-thread_kaizen_paper.pdf