«前の日記(2008-03-05) 最新 次の日記(2008-03-07)» 編集

Matzにっき


2008-03-06 [長年日記]

_ 取材

「ワールドビジネスサテライト」の取材。

Rubyの、ということではなく、「クラウドソーシング」について オープンソース経験者という視点から聞きたかったらしい。

が、そもそもクラウドソーシングなんて言葉を知らない私は 聞かれてからあわてて検索したりして。 役に立つんだか、立たないんだか。

で、あわてて調べたクラウドソーシングについては、 一時「オープンソースにすれば「コミュニティ」が手伝ってくれてどんどん発展する」と 「誤解」されていたのと、同じ臭いを感じるが気のせいだろうか。

一応、インセンティブが重要というコメントを付けておいたが、 最終的にどういう扱いになるんだか不安なものである。

_ [Ruby] GCの改善について

あちこちでRuby(MRI)のGCについてけなされている。

まあ、たくさんのリソースをかけているJVMのGCに勝つのは 最初から無理な相談なんだが、とはいえ問題があるのであれば 改善したいのが技術者魂というものだ。

指摘されているRuby GCの「課題」は以下のようなものがある。

  • スループット
  • 停止時間
  • メモリリーク
  • プロセスサイズ
  • copy-on-writeとの相性

具体的に問題が生じているものもあれば 理論的可能性のものもあるが、まあ、問題がないとは言わない。

それぞれについて、もう少し解説した上で、 考えられる対策についても述べよう。

スループット

プログラムの実行時間全体の中で、 GCで消費される時間の比をスループットと呼ぶ。 これはGC全体の性能を意味する。

これを根本的に削減する方法はあまりないのだが、 世代別GCが有効だといわれている。

問題は世代別GCを正しく実装するためには 古い世代から新しい世代への参照を検出する必要があり、 そのためにはオブジェクトの書き換えをフックする「ライトバリアー(write barrier)」を 導入しなければならないこと。

以前のRubyに対して世代別GCを実装した結果では、 このライトバリアーのコストが高くて結果的に性能が低下してしまった。

とはいえ、現在の実装でスループットが悪くて使えないというケースは ほとんど聞いたことがないので、それほど強い動機づけはないかもしれない。

停止時間

通常の実装ではGC中には他の作業を行うことができないので、 リアルタイム性の高い処理中にGCが発生すると 処理が引っかかったような印象を与えることがある。 ここで重要になるのが「停止時間」である。

停止時間には「平均停止時間」と「最大停止時間」があり、 応答性に重要なのは最大停止時間の方。 またハードリアルタイム領域では、ただ単に短いだけでなく 予想可能である(たとえば最大100msで終了するとか)であることが 重要である、らしい。

世代別GCではスキャンするオブジェクトが減るため、 平均停止時間は減少するが、フルGCにはそれなりにコストがかかるため 最大停止時間は減少しない。

とはいえ、リアルタイム性が必要な領域でRubyを使うというケースは (まだ)あまり多くないので、停止時間が問題になることもそんなにないような気がする。

もし本当に問題になることがあるならば、 まずは現在のGCのままスイープフェーズをオンデマンドで行うことで、 GC時間をマーク時間だけに限定でき、停止時間を削減できる。

メモリリーク

これは良く指摘されるのだが、 Rubyは保守的GCを使っているので、 本当は参照されていないはずのオブジェクトが参照されていると見なされて いつまでも解放されない(ので結果的にメモリリークになる)ことが ときどき観測されるのだという。

これは確かに私も見たことがある。 個人的には問題だったことはないけど、 サーバープロセスのような長期間生きるプログラムだと 問題になるかもしれない。

で、そういう問題が起きた時の状態をデバッガで観測した経験からいうと Rubyにおけるリークは保守的GC固有の問題(整数値がアドレスと区別できないのでとりあえず生きていると見なす)というよりも、 Cスタックに参照が残っていて生きていると見なされているようだ。 スタック先頭からスタックボトムまで全領域をルートとして 扱っているのが「無駄な参照」を検出してしまう大きな理由なのだろう。

スキャンするCスタックを減らせばよいのだろうが、 スタックの構造はCPUやOSに依存するので、 移植性が下がることになりかねない。

悩ましい。

可能性としては

  • 基本的に保守的にマークする必要があるのはCで実装されたメソッドが使っているスタックだから、 そこだけを記録してルートにする。面倒だが移植性はありそう
  • greenletなどを 参照にOSごとCPUごとにルートを得る。知らないOSでは現状のまま

がある。時間が取れれば考えてみる価値はありそう。

プロセスサイズ

メモリリークとは別に長時間動き続けるRubyプロセスは肥大化する可能性がある。 これはRubyのメモリアロケータがオブジェクトのための領域をmallocする一方で なかなかfreeしないからである。

現状ではオブジェクトのためにheapと呼ばれる領域を割り当てているが ヒープに存在するオブジェクトがすべて解放された時だけ heap領域をfreeしている。 が、Ruby GCはオブジェクトの移動を行わないため、 現在の割り当て方針ではheapがfreeされる可能性はかなり低い。

現在、10000オブジェクト(2回目以降は前回のサイズの1.8倍)ぶん割り当ててるheapのサイズを もっと小さくすればfreeされるチャンスは増す。 もっともあまりfreeされすぎてもmallocとfreeの輻輳が起きて 効率が悪くなってしまうだろうけど。

あと、heapサイズを小さくすると保守的GCの使うオブジェクト判定のコストが上がってしまう 可能性がある。最近、trunkでオブジェクト判定にbsearchを導入したのは このheapサイズ縮小のための伏線である。

copy-on-writeとの相性

マーク・アンド・スイープGCでは、オブジェクトが生きているかどうかを判定するために 各オブジェクトごとにマークビットを設定している。 現状ではこのマークビットはオブジェクト内部に用意されているのだが、 考えてみるとGCごとにすべてのオブジェクトが(少なくとも1ビットは)書き換えられていることを 意味する。

これはUNIX系OSが備えているCopy-on-writeとすこぶる相性が悪い。 せっかく変更されないデータはプロセス間で共有しようとしているのに、 GCが起きるとオブジェクト領域が全部コピーされてしまう。

これだはfork(子プロセス生成)を多用するプログラムの効率が低下してしまう。 で、これについてはパッチを用意したのだが、 heapサイズ削減と相性が悪いし、それでなくてもGC性能が低下しそうである。 どうしたもんだか。

あと、そろそろ発売される日経Linux 4月号でもGCについて解説している。

本日のツッコミ(全16件) [ツッコミを入れる]
_ ジョン・ドゥ (2008-03-11 14:42)

よければ放送予定の日にちを教えてもらえないでしょうか?

_ まつもと (2008-03-11 18:47)

15日、土曜日でした。手遅れですね。ごめんなさい。

_ ささだ (2008-03-11 22:26)

最大停止時間は減少したほうがいいんじゃないでしょうか。

_ まつもと (2008-03-12 00:35)

減少しない方がよいわけはないのですが、現状でも平気なケースは多いのも事実です。と思ったらLazySweepを実装したというトラックバックが...。

_ まつもと (2008-03-12 00:53)

よく見たらトラックバックじゃなかった。URL貼っときます<br>http://d.hatena.ne.jp/authorNari/20080311/1205242360

_ kenn (2008-03-12 03:17)

Rails+FastCGIだと、プロセスサイズの肥大化は結構致命的だったりします。Rubyプロセスのいくつかが500MBぐらいになると、いくらメモリ積んでてもswapがはじまってしまいます。リークも疑って調べてみたのですが特定しきれなかったので当面swapがはじまったら自動的にkillすることで対症療法してます。サーバで使ってる立場からすると、スループットや停止時間の改善はnice-to-haveで、リークとプロセスサイズの問題はクリティカル、ということは言えるかも知れません。この問題にaddressしていただけるならとても嬉しいです。

_ まつもと (2008-03-12 06:58)

そうですか。近いうちに(ここで)パッチを公開しますから、その時は試してみてくださいね。

_ ibook (2008-03-12 11:32)

停止時間:フルGCにはそれなりにコストがかかるため最大停止時間は減少する。<br>すみません、ここよくわかりません。最後は"増加"する?<br><br>メモリリーク:greenletなどを 参照にOSごとCPUごとにルーとを得る。<br>ルートでしょうか?

_ authorNari (2008-03-12 13:46)

LazySweepを実装したものです。<br>停止時間が予想していたよりも改善されずに残念でした。<br>ただ、Heapサイズが大きくなってから停止時間を抑えるGCに切り替わるというのは個人的にはいいのかなぁと思っています。<br>次はconcurrentかincrementalなGCを作ってみたいです。<br>最大停止時間の改善についてやっきになって考えていたのですが、<br>確かにHeapが大きくなりすぎる方が問題ですね。<br>プロセスサイズ対応のパッチ楽しみにしています。

_ まつもと (2008-03-13 00:09)

ibookさん、両方とも間違いでした。今は修正しています。<br>ありがとうございました。<br><br>authorNariさん、続きはruby-devで。

_ DSS (2008-03-13 11:22)

停止時間で問題なのは平均でも最大でもなく『頻度』との積では?!

_ oo (2008-03-13 11:48)

>『頻度』との積では?!<br>それはまつもとさんが「スループット」と書いている物ですよね。それが問題でないわけではないです。<br>ただリアルタイム処理では最大時間が問題。「一時間ごとに0.01秒かかります」はOKだけど「年に一回だけ10秒かかります」だとアウトのケースもあるということ。

_ ひろのぶ (2008-03-13 13:16)

copy-on-writeとの相性に関しては根本的にどうにかして欲しいです。

_ (ぱ) (2008-03-14 01:17)

ええと、すみません。<br>>サーバープロセスのような長期間生きるプログラムだと問題になるかもしれない。<br><br>RubyのGCがConservative GCなのでメモリリークが起きるということは認識していましたが、これでリークするオブジェクトの数は、「その時点でのスタックのサイズに比例する」と思っていました。いったんリークしたオブジェクトでも、その後スタックが縮めば正しくゴミと解釈されるのではないでしょうか?<br>だとすれば、リークするオブジェクトの数は、プロセスの生存期間とは無関係で、「サーバープロセスのような長期間生きるプログラム」かどうかは関係ないのではないでしょうか。もっともそれにより古いオブジェクトが残ることで、該当のheapが開放されない問題はあるかもしれませんけど。

_ まつもと (2008-03-14 01:32)

(ぱ)さん、Rubyの場合、スレッドとかでスタックがコピーされることがあって、予想以上に長い間参照されることもあるようです。

_ 元職業プログラマ (2008-03-15 11:34)

ミッションクリティカルなシステムのプロセスにおいて、プロセスサイズが増加して行くのは、非常に問題です。<br>そこで提案ですが、RubyもC++のdeleteの様に、オブジェクトを明示的に開放出来る機構を追加するというのは如何でしょうか?

お名前:
E-mail:
コメント:
[]

«前の日記(2008-03-05) 最新 次の日記(2008-03-07)» 編集

track feed Matzにっき Creative Commons License This work is licensed under a Creative Commons License.