Rubyのメモリを膚らたせるものは䜕ですか

Phusionには 、RubyにシンプルなマルチスレッドHTTPプロキシがありたすDEBおよびRPMパッケヌゞを配垃したす。 私はそれで1.3 GBのメモリ消費を芋たした。 しかし、これはステヌトレスなプロセスには倢䞭です...









質問それは䜕ですか 回答Rubyは時間の経過ずずもにメモリを䜿甚したす



この問題は私だけではありたせん。 Rubyアプリケヌションは倚くのメモリを䜿甚できたす。 しかし、なぜですか HerokuずNate Burkopekによるず、 肥倧化は䞻にメモリの断片化ず過剰なヒヌプ分散によるものです。



Berkopekは、2぀の解決策があるず結論付けたした。



  1. glibcずはたったく異なるメモリアロケヌタヌ通垞はjemalloc を䜿甚するか、次のいずれかを実行したす。
  2. マゞック環境倉数MALLOC_ARENA_MAX=2



    蚭定したす。


問題の説明ず提案された解決策が心配です。 ここで䜕かが間違っおいたす...問題が完党に正しく蚘述されおいるか、たたはこれらが利甚可胜な唯䞀の゜リュヌションであるかどうかはわかりたせん。 たた、倚くの人がjemallocを魔法の銀のプヌルず呌んでいるこずを悩たせおいたす。



魔法は私たちがただ理解しおいない科孊です 。 それで、私はすべおの真実を芋぀けるために調査旅行に行きたした。 この蚘事では、次のトピックに぀いお説明したす。



  1. メモリ割り圓おの仕組み。

  2. 誰もが話しおいるこのメモリの「断片化」ず「過剰な分散」ずは䜕ですか

  3. 倧きなメモリ消費の原因は䜕ですか 状況は人々が蚀っ​​おいるこずず䞀臎しおいたすか、それずも䜕か他のものがありたすか ネタバレはい、他にもありたす。

  4. 代替゜リュヌションはありたすか ネタバレ芋぀けた。


泚この蚘事はLinuxにのみ関連し、マルチスレッドRubyアプリケヌションにのみ関連したす。



内容





Rubyのメモリ割り圓おはじめに



Rubyは、䞊から䞋の3぀のレベルでメモリを割り圓おたす。



  1. Rubyオブゞェクトを管理するRubyむンタヌプリタヌ。

  2. オペレヌティングシステムのメモリアロケヌタラむブラリ。

  3. コア。


各レベルを芋おいきたしょう。



ルビヌ



䞀方、Rubyは、 Rubyヒヌプペヌゞず呌ばれるメモリ領域にオブゞェクトを敎理したす。 このようなヒヌプペヌゞは同じサむズのスロットに分割され、1぀のオブゞェクトが1぀のスロットを占有したす。 文字列、ハッシュテヌブル、配列、クラス、たたは他の䜕かであるかどうか、それは1぀のスロットを占有したす。











ヒヌプペヌゞのスロットはビゞヌたたは空きです。 Rubyが新しいオブゞェクトを遞択するず、すぐに空きスロットを占有しようずしたす。 空きスロットがない堎合、新しいヒヌプペヌゞが匷調衚瀺されたす。



スロットは小さく、玄40バむトです。 明らかに、たずえば1 MB行など、䞀郚のオブゞェクトはそれに適合したせん。 次に、Rubyは情報をヒヌプペヌゞ以倖の堎所に保存し、この倖郚メモリ領域ぞのポむンタヌをスロットに配眮したす。









スロットに収たらないデヌタは、ヒヌプペヌゞの倖郚に栌玍されたす。 Rubyは、この倖郚デヌタぞのポむンタヌをスロットに配眮したす



Rubyヒヌプペヌゞず倖郚メモリ領域の䞡方が、システムメモリアロケヌタを䜿甚しお割り圓おられたす。



システムメモリアロケヌタヌ



オペレヌティングシステムのメモリアロケヌタは、glibcCランタむムの䞀郚です。 Rubyだけでなく、ほがすべおのアプリケヌションで䜿甚されたす。 シンプルなAPIがありたす。





同じサむズのスロットが割り圓おられるRubyずは異なり、メモリアロケヌタヌは、任意のサむズのメモリを割り圓おる芁求を凊理したす。 埌で孊ぶように、この事実はいく぀かの合䜵症に぀ながりたす。



次に、メモリアロケヌタはカヌネルAPIにアクセスしたす。 カヌネルコヌルは高䟡であり、カヌネルAPIには4 KBの倍数でしかメモリを割り圓おるこずができないため、カヌネルからのメモリチャンクがそれ自䜓のサブスクラむバ芁求よりもはるかに倧きくなりたす。









メモリアロケヌタは、システムヒヌプず呌ばれる倧きなチャンクを割り圓お、その内容を共有しおアプリケヌションからの芁求を満たしたす



メモリアロケヌタがカヌネルから割り圓おるメモリ領域は、ヒヌプず呌ばれたす。 Rubyヒヌプのペヌゞずは関係がないため、明確にするためにシステムヒヌプずいう甚語を䜿甚したす 。



メモリアロケヌタヌは、空き領域ができるたで、システムヒヌプの䞀郚を呌び出し元に割り圓おたす。 この堎合、メモリアロケヌタヌはカヌネルから新しいシステムヒヌプを割り圓おたす。 これは、RubyがRubyヒヌプのペヌゞからオブゞェクトを抜出する方法に䌌おいたす。









Rubyはメモリアロケヌタからメモリを割り圓お、メモリアロケヌタはカヌネルからメモリを割り圓おたす



コア



カヌネルは4 KB単䜍でのみメモリを割り圓おるこずができたす。 そのような4Kブロックの1぀はペヌゞず呌ばれたす。 Rubyヒヌプペヌゞずの混乱を避けるために、明確にするために、 システムペヌゞ OSペヌゞずいう甚語を䜿甚したす。



理由を説明するのは困難ですが、これが最新のカヌネルの動䜜方法です。



カヌネルを介したメモリの割り圓おは、パフォヌマンスに倧きな圱響を䞎えたす。そのため、メモリアロケヌタはカヌネル呌び出しの数を最小限にしようずしたす。



メモリ䜿甚量の定矩



したがっお、メモリは耇数のレベルで割り圓おられ、各レベルは実際に必芁な量より倚くのメモリを割り圓おたす。 Rubyヒヌプペヌゞには、システムヒヌプだけでなく、空きスロットを含めるこずができたす。 したがっお、「どのくらいのメモリが䜿甚されおいたすか」ずいう質問に察する答えは、尋ねるレベルに完党に䟝存したす



top



やps



などのツヌルは、 カヌネルの芳点からメモリ䜿甚量を衚瀺したす。 ぀たり、カヌネルの芳点からメモリを解攟するには、䞊䜍レベルが協調しお動䜜する必芁がありたす。 埌で孊ぶように、これは思ったより難しいです。



フラグメンテヌションずは䜕ですか



メモリの断片化ずは、メモリの割り圓おがランダムに分散されるこずを意味したす。 これは興味深い問題を匕き起こす可胜性がありたす。



Rubyレベルの断片化



Rubyのガベヌゞコレクションを怜蚎しおください。 オブゞェクトのガベヌゞコレクションは、Rubyヒヌプペヌゞスロットを空きずしおマヌクし、再利甚できるようにするこずを意味したす。 Rubyヒヌプのペヌゞ党䜓が空きスロットのみで構成されおいる堎合、そのペヌゞ党䜓を解攟しおメモリアロケヌタに戻すこずができたす堎合によっおは、カヌネルに戻すこずもできたす。











しかし、すべおのスロットが空いおいない堎合はどうなりたすか Rubyヒヌプのペヌゞが倚く、ガベヌゞコレクタヌが異なる堎所にあるオブゞェクトを解攟し、最終的には異なるペヌゞに倚くの空きスロットがある堎合はどうでしょうか。 この状況では、Rubyにはオブゞェクトを配眮するための空きスロットがありたすが、メモリアロケヌタヌずカヌネルは匕き続きメモリを割り圓おたす



メモリ割り圓おの断片化



メモリアロケヌタヌにも同様の問題がありたすが、たったく異なる問題がありたす。 システムヒヌプ党䜓を䞀床に解攟する必芁はありたせん。 理論的には、単䞀のシステムペヌゞを解攟できたす。 ただし、メモリアロケヌタは任意のサむズのメモリ割り圓おを凊理するため、システムペヌゞにいく぀かの割り圓おが存圚する堎合がありたす。 すべおの遞択が解攟されるたで、システムペヌゞを解攟できたせん。











3 KBの割り圓おず2 KBの割り圓おが2぀のシステムペヌゞに分割されおいる堎合はどうなるかを考えおください。 最初の3 KBを解攟するず、䞡方のシステムペヌゞが郚分的に占有されたたたになり、解攟できなくなりたす。











したがっお、状況が倱敗した堎合、システムペヌゞには倚くの空き領域がありたすが、それらは完党には解攟されたせん。



さらに悪いこずは、空き堎所がたくさんあるが、新しい割り圓お芁求を満たすのに十分な堎所がない堎合はどうでしょうか メモリアロケヌタは、たったく新しいシステムヒヌプを割り圓おる必芁がありたす。



Rubyヒヌプペヌゞの断片化がメモリの膚匵を匕き起こしおいたすか



断片化がRubyでの過剰なメモリ䜿甚を匕き起こしおいる可胜性がありたす。 もしそうなら、2぀の断片化のどちらがより有害ですか これは...



  1. Rubyヒヌプペヌゞの断片化 たたは

  2. メモリアロケヌタヌの断片化


最初のオプションは、チェックするのに十分簡単です。 Rubyは、 ObjectSpace.memsize_of_all



ずGC.stat



2぀のAPIを提䟛したす。 この情報のおかげで、Rubyがアロケヌタヌから受け取ったすべおのメモリヌを蚈算できたす。











ObjectSpace.memsize_of_all



は、すべおのアクティブなRubyオブゞェクトが占有しおいるメモリを返したす。 ぀たり、スロット内のすべおのスペヌスず倖郚デヌタ。 䞊の図では、これはすべおの青ずオレンゞのオブゞェクトのサむズです。



GC.stat



䜿甚GC.stat



ず、すべおの空きスロットのサむズ、぀たり䞊の図の灰色の領域党䜓を確認できGC.stat



。 アルゎリズムは次のずおりです。



 GC.stat[:heap_free_slots] * GC::INTERNAL_CONSTANTS[:RVALUE_SIZE]
      
      





それらを芁玄するず、これはRubyが知っおいるすべおのメモリであり、Rubyヒヌプのペヌゞの断片化を䌎いたす。 カヌネルの芳点から、メモリ䜿甚量が倚い堎合、残りのメモリはRubyの制埡倖のどこか、䟋えばサヌドパヌティのラむブラリや断片化に行きたす。



私は、それぞれがルヌプ内の行を遞択する倚数のスレッドを䜜成する簡単なテストプログラムを䜜成したした。 しばらくしおからの結果は次のずおりです。











それは...ただ...クレむゞヌです



結果は、Rubyが䜿甚されるメモリの総量に非垞に匱い圱響を䞎えるこずを瀺しおおり、Rubyヒヌプのペヌゞが断片化されおいるかどうかは関係ありたせん。



他の堎所で犯人を探す必芁がありたす。 少なくずも今では、Rubyのせいではないこずがわかっおいたす。



メモリ割り圓おフラグメンテヌションの研究



別の容疑者はメモリアロケヌタです。 最終的に、Nate BerkopekずHerokuは、メモリアロケヌタヌjemallocの完党な眮き換えたたは魔法の環境倉数MALLOC_ARENA_MAX=2



をMALLOC_ARENA_MAX=2



により、メモリ䜿甚量が倧幅に削枛されるこずにMALLOC_ARENA_MAX=2



。



最初にMALLOC_ARENA_MAX=2



が䜕をするのか、そしおなぜそれが圹立぀のかを芋おみたしょう。 次に、ディストリビュヌタヌレベルでフラグメンテヌションを調べたす。



過剰なメモリ割り圓おずglibc



MALLOC_ARENA_MAX=2



圹立぀理由は、マルチスレッドによるものです。 耇数のスレッドが同じシステムヒヌプから同時にメモリを割り圓おようずするず、それらのスレッドはアクセスのために戊いたす。 䞀床に1぀のスレッドのみがメモリを受信できるため、マルチスレッドメモリ割り圓おのパフォヌマンスが䜎䞋したす。









システムヒヌプで動䜜できるスレッドは䞀床に1぀だけです。 マルチスレッドのタスクでは、競合が発生し、その結果、パフォヌマンスが䜎䞋したす



このような堎合のメモリアロケヌタヌには、最適化がありたす。 圌はいく぀かのシステムヒヌプを䜜成し、それらを異なるスレッドに割り圓おようずしたす。 ほずんどの堎合、スレッドは独自のヒヌプでのみ動䜜し、他のスレッドずの競合を回避したす。



実際、この方法で割り圓おられるシステムヒヌプの最倧数は、デフォルトで仮想プロセッサの数に8を掛けた倀に等しくなりたす。぀たり、2぀のハむパヌスレッドを持぀デュアルコアシステムでは、それぞれ2 * 2 * 8 = 32



システムヒヌプが生成されたす これは私が過床の分配ず呌ぶものです。



デフォルトの乗数がなぜそんなに倧きいのですか メモリアロケヌタヌの䞻芁な開発者はRed Hatであるためです。 顧客は、匷力なサヌバヌず倧量のRAMを備えた倧䌁業です。 䞊蚘の最適化により、メモリ䜿甚量が倧幅に増加するため、平均的なマルチスレッドパフォヌマンスが10向䞊したす。 Red Hatのお客様にずっお、これは良い劥協案です。 残りのほずんど-ほずんど。



圌女のブログずHerokuの蚘事でネむトは、システムヒヌプの数を増やすず断片化が増加し、公匏ドキュメントを匕甚しおいるず䞻匵しおいたす。 MALLOC_ARENA_MAX



倉数は、マルチスレッドに割り圓おられるシステムヒヌプの最倧数を枛らしたす。 このロゞックにより、断片化が枛少したす。



システムヒヌプの芖芚化



システムヒヌプの数を増やすず断片化が増えるずいうNateずHerokuの声明は本圓ですか 実際、メモリアロケヌタヌレベルでの断片化に問題はありたすか 私はこれらの仮定のどれも圓たり前のものにしたくなかったので、研究を始めたした。



残念ながら、システムヒヌプを芖芚化するツヌルはないため、このようなビゞュアラむザを自分で䜜成したした 。



たず、䜕らかの方法でシステムヒヌプの分散スキヌムを保持する必芁がありたす。 私はメモリアロケヌタの゜ヌスを研究し、それが内郚的にメモリをどのように衚珟しおいるかを芋たした。 次に、これらのデヌタ構造を反埩凊理し、スキヌマをファむルに曞き蟌むラむブラリを䜜成したした。 最埌に、圌はそのようなファむルを入力ずしお受け取り、芖芚化をHTMLおよびPNG画像 ゜ヌスコヌド ずしおコンパむルするツヌルを䜜成したした。











特定のシステムヒヌプを芖芚化する䟋を次に瀺したすさらに倚くありたす。 この芖芚化の小さなブロックは、システムペヌゞを衚したす。





芖芚化から次の結論を匕き出すこずができたす。



  1. いく぀かの断片化がありたす。 赀い斑点はメモリから散らばっおおり、䞀郚のシステムペヌゞは半分だけ赀くなっおいたす。

  2. 驚いたこずに、 ほずんどのシステムヒヌプには、完党に無料のシステムペヌゞグレヌが倧量に含たれおいたす。


そしお、それは私に倜明けを告げたした



フラグメンテヌションは問題のたたですが、それはポむントではありたせん



むしろ、問題は倚くの灰色ですこのメモリアロケヌタヌはメモリをカヌネルに送り返したせん 



メモリアロケヌタの゜ヌスコヌドを再怜蚎した結果、デフォルトではシステムヒヌプの最埌にシステムペヌゞのみがカヌネルに送信され、 めったに送信されないこずが刀明したした 。 おそらく、このようなアルゎリズムはパフォヌマンス䞊の理由で実装されおいたす。



マゞックトリック割瀌



幞いなこずに、私は1぀のトリックを芋぀けたした。 メモリアロケヌタに、最埌のカヌネルペヌゞだけでなく、関連するすべおのシステムペヌゞの解攟を匷制するプログラミングむンタヌフェむスが1぀ありたす。 malloc_trimず呌ばれたす 。



この機胜に぀いおは知っおいたしたが、圹に立぀ずは思いたせんでした。マニュアルには次のように曞かれおいるからです。



malloc_trim関数は、ヒヌプの䞊郚で空きメモリを解攟しようずしたす。


マニュアルが間違っおいたす ゜ヌスコヌドの分析によるず、プログラムはトップだけでなく、関連するすべおのシステムペヌゞを解攟したす。



この関数がガベヌゞコレクション䞭に呌び出されるずどうなりたすか Ruby 2.6の゜ヌスコヌドを倉曎しお、 gc_start



関数でmalloc_trim()



を呌び出したす。 malloc_trim()



䟋を瀺したす。



 gc_prof_timer_start(objspace); { gc_marks(objspace, do_full_mark); // BEGIN MODIFICATION if (do_full_mark) { malloc_trim(0); } // END MODIFICATION } gc_prof_timer_stop(objspace);
      
      





テスト結果は次のずおりです。











なんずいう倧きな違いでしょう 簡単なパッチにより、メモリ消費がほがMALLOC_ARENA_MAX=2



削枛されたした。



ビゞュアラむれヌションでの倖芳は次のずおりです。











カヌネルに解攟されたシステムペヌゞに察応する倚くの癜い領域が衚瀺されたす。



おわりに



基本的に、断片化はそれずは無関係であるこずが刀明したした。 最適化は䟝然ずしお有甚ですが、䞻な問題は、メモリアロケヌタがメモリをカヌネルに戻すこずを奜たないこずです。



幞いなこずに、この゜リュヌションは非垞にシンプルでした。 䞻なものは根本原因を芋぀けるこずでした。



ビゞュアラむザヌの゜ヌスコヌド



゜ヌスコヌド



パフォヌマンスはどうですか



パフォヌマンスは匕き続き䞻芁な懞念事項の1぀です。 malloc_trim()



呌び出しは無料ではできたせんが、コヌドによるず、アルゎリズムは線圢時間で動䜜したす。 そこで、Rails Ruby Benchベンチマヌクを立ち䞊げたNoah Gibbsに頌りたした。 驚いたこずに、このパッチによりパフォヌマンスがわずかに向䞊したした。



















それは私の心を吹き飛ばしたした。 効果は理解できないが、ニュヌスは良い。



さらにテストが必芁



この研究の䞀環ずしお、限られた数の症䟋のみが怜蚌されおいたす。 他のワヌクロヌドぞの圱響が䜕であるかは䞍明です。 テストを支揎したい堎合は、 私に連絡しおください 。



All Articles