Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rubyでの実用的なガベージコレクションのチューニング

以下の投稿は、2017年のNateBerkopecによる「UnderstandingRubyGCthroughGC.stat」という記事に基づいていることがわかりました。この記事の一部は悩まされていたようです。これは、元の著者が言及するまで私たちが気付いていなかったことです。公開する前に、すべての記事を盗用ツールで実行しましたが、これは検出されませんでした。この不注意によるエラーについて、ネイトと読者に多大な謝罪を申し上げます。

アプリのパフォーマンスを完全に制御するには、Rubyでガベージコレクションがどのように機能するかを理解することが重要です。

この投稿では、Rubyでガベージコレクションを実装およびカスタマイズする方法について詳しく説明します。

始めましょう!

Rubyガベージコレクタモジュール

Ruby Garbage Collectorモジュールは、Rubyのマークアンドスイープガベージコレクションメカニズムへのインターフェイスです。

必要に応じてバックグラウンドで自動的に実行されますが、GCモジュールを使用すると、必要に応じて手動でGCを呼び出し、ガベージコレクションサイクルがどのように実行されているかを把握できます。このモジュールは、中程度のパフォーマンスに変更できるいくつかのパラメーターを提供します。

このモジュールで最も一般的に使用される方法のいくつかは次のとおりです。

  • start / garbage_collect :このメソッドは、ガベージコレクションサイクルを手動で開始します。
  • 有効/無効 :これらのメソッドは、自動ガベージコレクションサイクルを有効または無効にします。操作が成功したかどうかを示すブール値を返します。
  • 統計 :このメソッドは、GCモジュールのパフォーマンスを説明するキーと値のリストを提供します。これらの指標については、次のセクションで詳しく説明します。

Rubyガベージコレクターのパラメーターについて

RubyのGCが内部でどのように機能するかを理解するために、GCモジュールのメトリックを見てみましょう。新しく起動したirbで次のコマンドを実行します。

puts GC.stat

画面に次のような数字が表示されます。

{
    :count=>12,
    :heap_allocated_pages=>49,
    :heap_sorted_length=>49,
    :heap_allocatable_pages=>0,
    :heap_available_slots=>19975,
    :heap_live_slots=>19099,
    :heap_free_slots=>876,
    :heap_final_slots=>0,
    :heap_marked_slots=>16659,
    :heap_eden_pages=>49,
    :heap_tomb_pages=>0,
    :total_allocated_pages=>49,
    :total_freed_pages=>0,
    :total_allocated_objects=>66358,
    :total_freed_objects=>47259,
    :malloc_increase_bytes=>16216,
    :malloc_increase_bytes_limit=>16777216,
    :minor_gc_count=>10,
    :major_gc_count=>2,
    :remembered_wb_unprotected_objects=>191,
    :remembered_wb_unprotected_objects_limit=>312,
    :old_objects=>16024,
    :old_objects_limit=>23556,
    :oldmalloc_increase_bytes=>158824,
    :oldmalloc_increase_bytes_limit=>16777216
}

これは、実行時にガベージコレクションがどのように行われているかに関するすべての情報を保持します。これらの各数値を詳しく調べてみましょう。

Rubyガベージコレクターのカウント

まず、次のキーについて説明します。

{
    :count=>12,
    #…
    :minor_gc_count=>10,
    :major_gc_count=>2,
}

これらはGCカウントであり、非常に簡単な情報を伝えます。 minor_gc_count およびmajor_gc_count 実行されたガベージコレクションの各タイプのカウントです。

Rubyには2種類のガベージコレクションがあります。

マイナーGCとは、新しいオブジェクト、つまり3回以下のガベージコレクションサイクルで生き残ったオブジェクトのみをガベージコレクションしようとするガベージコレクションの試みを指します。

一方、メジャーGCは、3回以上のガベージコレクションサイクルを生き延びたオブジェクトも含めて、すべてのオブジェクトをガベージコレクションしようとするガベージコレクションの試みです。 count minor_gc_countの合計です およびmajor_gc_count

GCカウントの追跡は、いくつかの理由で役立ちます。特定のジョブまたはプロセスが常にGCをトリガーするかどうか、およびそれがGCをトリガーする回数を把握できます。マルチスレッドアプリケーションのような場合は100%正確ではないかもしれませんが、メモリがどこで出血しているのかを把握するための良い出発点です。

ヒープ番号:スロットとページ

次に、これらのキー(ヒープ番号とも呼ばれます)について説明します。 :

{
    # page numbers
    :heap_allocated_pages=>49,
    :heap_sorted_length=>49,
    :heap_allocatable_pages=>0,
 
    # slots
    :heap_available_slots=>19975,
    :heap_live_slots=>19099,
    :heap_free_slots=>876,
    :heap_final_slots=>0,
    :heap_marked_slots=>16659,
 
    # Eden and Tomb pages
    :heap_eden_pages=>49,
    :heap_tomb_pages=>0,
}

ここで話しているヒープはCデータ構造です。現在稼働中のすべてのRubyオブジェクトへの参照が含まれています。ヒープページ はメモリスロットで構成され、各スロットには1つのライブRubyオブジェクトに関する情報のみが含まれます。

  • heap_allocated_pages 現在割り当てられているヒープページの数です。これらのページは、完全に空、完全に埋められている、または部分的に埋められている可能性があります。
  • heap_sorted_length ヒープがメモリ内で占有している実際のサイズであり、heap_allocated_pagesとは異なります。 、長さは長さであるため カウントではなく、まとめられたヒープページの数 。最初に10ページを割り当ててから、セットの中央から1ページを解放した場合、heap_allocated_pages 9になりますが、heap_sorted_length まだ10になります。
  • 最後に、heap_allocatable_pages は、Rubyが現在所有していて、必要なときに使用できるヒープの数です。

さて、スロットに来てください:

  • heap_available_slots ヒープページで使用可能なスロットの総数です。
  • heap_live_slots はメモリ内のライブオブジェクトの数です。
  • heap_free_slots 割り当てられたヒープページ内の空のスロットです。
  • heap_final_slots オブジェクトにファイナライザーがあるスロットの数です。 それらに添付されています。ファイナライザーは、オブジェクト指向のデストラクタと同様に、オブジェクトが解放されたときに実行されるProcです。
  • heap_marked_slots 古いオブジェクト(つまり、3 GCサイクルを超えて存在しているオブジェクト)と書き込みバリアの保護されていないオブジェクトの数です。

次に、tomb_pagesがあります およびeden_pages

tomb_pages ライブオブジェクトを含まないページの数です。これらのページは、最終的にRubyによってオペレーティングシステムにリリースされます。

一方、eden_pages は、少なくとも1つのライブオブジェクトを含むページの数であるため、オペレーティングシステムにリリースすることはできません。

メトリックheap_free_slotsを監視することを検討してください アプリケーションでメモリの肥大化の問題に直面した場合。

空きスロットの数が多い(250,000を超える)場合は、通常、一度に多数のオブジェクトを割り当ててからそれらを解放する少数のコントローラーアクションがあることを示します。これにより、実行中のRubyプロセスのサイズが恒久的に肥大化する可能性があります。

累積数

{
    :total_allocated_pages=>49,
    :total_freed_pages=>0,
    :total_allocated_objects=>66358,
    :total_freed_objects=>47259,
}

これらの数値は、プロセスの全期間にわたって累積的または加算的です。それらはGCによってリセットされることはなく、技術的にダウンすることはできません。これらの4つの数字はすべて自明です。

ガベージコレクションのしきい値

これらの数値を理解するには、最初にGCがトリガーされるタイミングを理解する必要があります。

{
    :malloc_increase_bytes=>16216,
    :malloc_increase_bytes_limit=>16777216,
    :remembered_wb_unprotected_objects=>191,
    :remembered_wb_unprotected_objects_limit=>312,
    :old_objects=>16024,
    :old_objects_limit=>23556,
    :oldmalloc_increase_bytes=>158824,
    :oldmalloc_increase_bytes_limit=>16777216
}

GCの実行は一定の間隔で行われるという一般的な想定とは異なり、GCの実行は、Rubyがメモリスペースを使い果たし始めたときにトリガーされます。マイナーGCは、Rubyがfree_slotsを使い果たしたときに発生します 。

Rubyのfree_slotsがまだ少ない場合 マイナーなGC実行後、またはoldmalloc、malloc、old object count、または shadyのしきい値 / write-barrier-unprotected countを超えました—メジャーGC実行がトリガーされます。 gc.statの上記の部分は、これらのしきい値の値を示しています。

malloc_increase_bytes ヒープの外部に割り当てられたメモリの量を指します これまで話しました。オブジェクトのサイズがメモリスロットの標準サイズ(たとえば、40バイト)を超える場合、Ruby malloc ■そのオブジェクトのためだけに別の場所にあるスペース。追加で割り当てられたスペースの合計がmalloc_increase_bytes_limitを超えた場合 、メジャーGCがトリガーされます。

oldmalloc_increase_bytes 古いオブジェクトの同様のしきい値です。 old_objects 古いとマークされたオブジェクトスロットの数です。数がold_objects_limitを超えた場合 、メジャーGCがトリガーされます。

remembered_wb_unprotected_objects 書き込みバリアによって保護されていないオブジェクトの総数です。 とは記憶されたセットの一部です 。

書き込みバリアは、Rubyランタイムとそのオブジェクト間のインターフェイスであり、インタプリタは、オブジェクトが作成されるとすぐに、オブジェクトへの参照とオブジェクトからの参照を追跡できます。

C拡張機能は、書き込みバリアを使用せずにオブジェクトへの新しい参照を作成できます。その場合、オブジェクトは日陰とマークされます。 または保護されていない書き込みバリア 。記憶されているセットは、単に古いのリストです。 newへの参照が少なくとも1つあるオブジェクト オブジェクト。

Rubyガベージコレクションのパフォーマンスをカスタマイズする

Ruby GCがアプリケーションのメモリを管理する方法を理解したので、次に、GCの動作をカスタマイズするために使用できるオプションを確認します。

Ruby GCのパフォーマンスを緩和し、アプリケーションのパフォーマンスを向上させるために使用できる環境変数は次のとおりです。

RUBY_GC_HEAP_INIT_SLOTS
RUBY_GC_HEAP_FREE_SLOTS
RUBY_GC_HEAP_GROWTH_FACTOR
RUBY_GC_HEAP_GROWTH_MAX_SLOTS
RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR
and other variables

ここで重要なパラメータについて1つずつ話しましょう:

  • RUBY_GC_HEAP_INIT_SLOTS :Rubyヒープのスロットの初期数を定義し、デフォルトで10000に設定されています。アプリが最初にほとんどのオブジェクトを割り当てることが確実な場合は、このパラメーターを変更することをお勧めします。
  • RUBY_GC_HEAP_FREE_SLOTS :GCサイクルの直後に使用可能でなければならない空きスロットの最小数を制御します。デフォルト値は4096です。この値は、実行時に最初のヒープ拡張中に1回だけ使用されます。
  • RUBY_GC_HEAP_GROWTH_FACTOR :Rubyインタープリターが利用できるヒープが増える要因。デフォルト値は1.8です。 Rubyはすでにヒープの成長に積極的であるため、これを変更してもほとんど意味がありません。最新のインタープリターではヒープがオンデマンドで割り当てられるため、これを減らしても大きな違いはありません。
  • RUBY_GC_HEAP_GROWTH_MAX_SLOTS :Rubyがヒープスペースに一度に追加できるスロットの最大数。デフォルト値は0で、これは数に制限がないことを意味します。アプリがその存続期間中に数百万のオブジェクトを割り当てる必要がある場合は、このパラメーターに上限を設けることをお勧めします。ただし、アプリのGC時間への影響はかなり低くなります。
  • RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR メモリ内の古いオブジェクトの総数=この数x最後のGCサイクル後のメモリ内の古いオブジェクトの数よりも多い場合に、インタプリタに主要なGCサイクルを実行させます。古い世代に入った後にオブジェクトの多くが未使用になると思われる場合は、この数を増やすことをお勧めします。ただし、これが必要になることはめったにありません。
  • RUBY_GC_MALLOC_LIMIT 新世代のmalloc呼び出しの最小制限です。デフォルト値は16MBです。 RUBY_GC_MALLOC_LIMIT_MAX 同じmalloc呼び出しの最大制限です。デフォルト値は32MBです。アプリケーションが平均よりも高いメモリを使用する場合は、これら2つの制限を増やすことをお勧めします。ただし、これらを上げすぎないように注意してください。そうしないと、ピークメモリ消費量が高くなる可能性があります。これらの制限は常に段階的に増やしてください。たとえば、4MBまたは8MB増やしてください。
  • RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR 新世代のmalloc制限の成長因子です。デフォルト値は1.4です。アプリがメモリを一度に全体ではなくチャンクで割り当てる場合は、この数を増やすことを検討する必要があります。
  • 同様に、RUBY_GC_OLDMALLOC_LIMIT およびRUBY_GC_OLDMALLOC_LIMIT_MAX 旧世代のmalloc制限の最小値と最大値です。これらのパラメータのデフォルト値は16MBと128MBです。
  • RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR この限界の成長因子です。デフォルト値は1.2です。最大の効果を得るために、これらを新しい世代の制限で変更することを検討できます。

Rubyでのガベージコレクションの微調整

アプリケーションの全体的なパフォーマンスを向上させるためにGCモジュールをカスタマイズするための一般的で簡単な方法について説明しました。ただし、これらの調整はすべての場合に機能するとは限りません。何をカスタマイズするかを決める前に、アプリのメモリ使用パターンを把握する必要があります。

反対に、これらのパラメーターの最適な値を見つける自動テストの実行を検討できます。 TuneMyGCのようなツールは、環境変数に最適な値のセットを見つけるのに非常に簡単です。

アプリケーションの動作がおかしい場合は、GCパラメータを確認してください。ここでの小さな変更は、アプリのメモリ消費量を減らし、メモリの膨張を防ぐのに大いに役立ちます。

この記事が、Rubyガベージコレクションモジュールをカスタマイズするときに注意すべき点についての良いアイデアを提供してくれることを願っています。紹介の詳細については、ガベージコレクションの概要パートIおよびパートIIをご覧ください。

コーディングを楽しんでください!


  1. Rubyの実用的なリンクリスト

    これは、「Rubyの実用的なコンピュータサイエンス」シリーズの3番目のエントリです。今日はリンクリストについてお話します。 では、リンクリストとは何ですか? 名前が示すように、リンクリストはデータをリスト形式で保存する方法です(ありがとう、キャプテンオブビシャス!)。 「リンクされた」部分は、データがノードに格納され、これらのノードが順番に相互にリンクされているという事実に由来します。 これはアレイとどう違うのですか? リンクリストと配列 リンクリストには、配列とは異なるパフォーマンス特性があります。これが、どちらかを選択する理由の1つです。 これは、リンクリストが配列よりも

  2. Rubyの実用的なグラフ理論

    これは「実用的なコンピュータサイエンス」シリーズの次回の記事で、Rubyを使用して実際の問題を解決するために古典的なコンピュータサイエンスの概念を適用する方法を学びます。 今日はグラフ理論についてお話します 。 二分木について聞いたことがあるかもしれませんが、次のようになります。 重要なのは、バイナリツリーはグラフの特殊なバージョンにすぎないため、グラフがどれほど普及しているかを知ることができるはずです。 グラフ理論の基礎の概要から始めましょう。次に、いくつかの実用的な使用法と、これをRubyで実装する方法を見ていきます。 グラフの基礎 グラフは2つの要素で構成されています: