Rubyのガベージコレクターの進化。 RGenGC

Koichi:Rubyのガベージコレクターの応答しきい値は8 MBです。 なぜこのような小さな値が使用されるのですか?

Matz:20年前、10 MBのメモリを搭載したマシンで作業していたからです。


パフォーマンスの問題は、Rubyコミュニティで常に最も話題になっているものの1つです。 負荷の高いウェブサイトであろうと、データをバックアップするための簡単なスクリプトであろうと、作業の速度は最も重要な特性です。 同時に、開発言語の機能と制限に関する知識は、最適化のための重要なアイデアのソースとして役立つことが多く、システムから最大限に引き出すことができます。



この記事では、Ruby言語の最も影響力のある部分の1つ、つまりガベージコレクター、その操作アルゴリズム、および言語の最新バージョンでの作業の改善について説明します。 最も一般的な「標準的な」Ruby実装、いわゆるMRIまたはCRubyについて話しています。



基本



プログラミング言語のガベージコレクション(GC)-プログラマの介入なしに自動メモリ管理を提供するメカニズム。 GCはRubyの特定の機能ではありません-同様のメカニズムが、Java、Python、C#などの最新の開発言語の大部分で使用されています。 MRIは、1960年に開発された古典的なマークアンドスイープガベージコレクションアルゴリズムを使用します。



Rubyはヒープを使用して、新しいオブジェクトにメモリを割り当てます。 オブジェクトの作成プロセスは、次の2つの条件のいずれかがmalloc_limit



と中断されmalloc_limit



。プログラムメモリが終了するか、特定のメモリ割り当てのしきい値(いわゆるmalloc_limit



ます。 この時点で、MRIはガベージコレクション、またはその最初の「マーク」フェーズを開始します。

画像






ガベージコレクターは、プログラムメモリ内のオブジェクトツリーを「ルート」オブジェクトから始めます。これらはグローバル変数または内部言語構造です。 オブジェクトの相互リンクをたどると、ガベージコレクターはオブジェクトを使用済みとしてマークします。 マークフェーズで使用済みとしてマークされていないデータはすべて「ガベージ」であり、それらが占有しているメモリは解放できます。 GCの2番目のスイープフェーズでメモリはガベージから解放されます。



ガベージコレクターの最適化。 Ruby 1.9.3および2.0



古典的なマークアンドスイープアルゴリズムにはいくつかの欠点があります。 まず第一に、これらはプログラムの動作の重要な一時停止であり、その間、有用なユーザーコードの実行が終了し、ガベージコレクターが動作します。 このような「世界の停止」が一時停止する時間が長くなると、エンドユーザーの観点からプログラムの実行が遅くなります。 ガベージコレクションに費やされる一時停止の長さを減らすために、Lazy SweepアルゴリズムがRuby 1.9.3に実装されました。 このバージョンのMRIから、ガベージコレクションのスイープステージは、不要なオブジェクトからメモリを完全に解放することではなく、プログラムを継続するために必要なメモリだけを解放します(新しいオブジェクトを作成します)。 次のオブジェクトを作成すると、一部のメモリが再び解放されます。







Ruby 2.0では、ガベージコレクターの別の拡張機能であるビットマップマーキングGCが統合されました。 このアルゴリズムは、 fork



を使用して子プロセスを作成するときにコピーオンライトメカニズムを使用するUnixシステムを対象としていfork



。 生成されたプロセスは、そのメモリが対応する親プロセスのメモリの「反映」にすぎないため、すばやく作成されます。 親プロセスと子プロセスのメモリの実際の分離は、共有メモリ領域にデータを書き込んだ後に発生します。 人気のあるUnicornアプリケーションサーバーやResqueバックグラウンドライブラリなど、多くの人気のあるRubyライブラリはfork



使用します。 ただし、MRIの従来のガベージコレクターは、マークフェーズ中に各オブジェクトの「使用可能性」フラグを設定し、ヒープのかなりの部分を変更するため、 fork



のセマンティクスにうまく適合しませんでした。プロセス。 Ruby 2.0では、フラグは、オブジェクトが使用されているかどうかに関係なく、別の構造(ビットマスク)で取り出され、オブジェクト自体とは別に保存されていました。 これによりfork



中にプロセスが共有するメモリ量を大幅に増やすことができ、コピーオンライトセマンティクスを活用する方が適切でした。



malloc_limit。 問題は8 MBです。



ガベージコレクターの最も重要な特性は、 malloc_limit



パラメーターmalloc_limit



が開始されるメモリ割り当てしきい値です。 ただし、この機能のデフォルト値は非常に小さく、8 MBでした。 最新のシステムでは、Webサイトでリクエストを処理すると、データベースから数十メガバイトのデータが選択されたり、大きなファイルが読み込まれたりする可能性があります。 同時に、ガベージコレクションが頻繁に行われるため、プログラムの速度が低下します。



一部では、この問題を解決するために、アプリケーションサーバー開発者は、ガベージコレクターの動作をより予測可能にし、HTTP要求の処理速度に対するGCの影響を最小限に抑えることを試みました。 これにより、 Unicorn OobGCPassenger Out-of-Band Workなどのテクノロジーが実現しました。 両方のソリューションは、リクエストの処理中にガベージコレクションを無効にし、リクエストがサーバーによって処理された後に強制します。 このようなメカニズムにも欠点がないわけではありません。要求が「軽量」の場合、GCは必要以上に実行される可能性があります。逆に、プロセスが大量のメモリを「消費」する可能性もあります。



Ruby 2.1 malloc_limit



機能が改訂されましたmalloc_limit



応答のしきい値がアプリケーションの動作に適応し始め、デフォルト値が16 MBに増加しました。 現在、 malloc_limit



はメモリの大量割り当てで自動的に変更され、その結果、ガベージコレクターの起動頻度が大幅に低下します。

画像






Ruby 2.1。 オブジェクトの世代



Ruby 2.1のリリースは、GCアルゴリズムの大幅な変更によって特徴付けられました。 世代ベースのガベージコレクターが言語に統合されました。 彼の作品は、プログラムで作成されたオブジェクトのほとんどが「死ぬ」という仮説に基づいています。 したがって、ガベージコレクターの主なアクティビティは、ライフサイクルが短いオブジェクトに関連付けられます。



Rubyでは、メモリは2世代のオブジェクトに分割されます-若いオブジェクトの世代と、少なくとも1つのガベージコレクションを生き残ったオブジェクトを含む古い世代です。 ほとんどの場合、ガベージコレクションは若い世代内でのみ実行されます。 メモリが不足した時点でのみ、両方の世代が参加して完全なガベージコレクションが実行されます。



このアプローチの重要な問題は、古い世代のオブジェクトから若い世代のオブジェクトへのバックリンクです。

画像






このようなリンクの可能性を考慮しない場合、マイナーGC-若い世代内のガベージコレクションは、本当に必要な使用済みオブジェクトをガベージとしてマークできます。 この問題を解決するために、問題のあるリンクが記憶されるRemember Setという特別な構造が使用されます。 そのようなリンクの発生を判断するために、Rubyインタープリターの読み取りに対する障壁が使用されます。 若い世代のオブジェクトへの参照がある「アダルト」オブジェクトは、マイナーガベージコレクションのルートオブジェクトとして使用されます。



MRIのガベージコレクションの重大な制限は、この言語用に開発された多くのC拡張機能のすべてとの下位互換性を維持する必要があることです。 それが、RubyのGCアルゴリズムがRGenGC-Restricted Generational GCと呼ばれる理由です。 ガベージコレクションの「制限」は、すべてのオブジェクトが2つのタイプに分けられることです。日陰のオブジェクトは、C拡張で使用される可能性のあるオブジェクトです。 したがって、ガベージコレクタは世代間で安全に移動できません。 そのようなオブジェクトが古い世代に該当する場合、以前の言語のセマンティクスではそのようなバリアを使用するためにC拡張機能を必要としなかったため、読み取りバリアによって保護されません。 その結果、上記の「問題のある」リンクが発生する可能性があります。



日陰のオブジェクトは、ガベージコレクションの世代に参加せず、通常のオブジェクトのみが世代間で移行します。 このソリューションにより、既存のCライブラリとの互換性を維持し、ガベージコレクターの開発を簡素化することができました。 ただし、新しいガベージコレクターの作業からかなりの数のオブジェクトが除外されます。 それでも、RGenGC開発者の測定によると、新しいガベージコレクターを使用した場合のアプリケーションの加速は約10%です。



今後の計画



ガベージコレクタは、この言語で最も急速に成長している部分の1つです。 近い将来、言語の世代数が3になります。 よりグローバルなものから、ガベージコレクションのマークフェーズとスイープフェーズに並列処理を導入する予定です。これにより、最新のマルチコアプロセッサの機能をより適切に使用できるようになります。



追加の情報源


Con田at一RubyConf 2013でのプレゼンテーション

顕微鏡でルビーを予約(2013)



All Articles