私はしばらくの間 Ruby を使用してきましたが、大きなプロジェクトの場合、かなりの量のメモリを占有する可能性があることがわかりました。Ruby でメモリ使用量を削減するためのベスト プラクティスは何ですか?
- 各回答に 1 つの「ベスト プラクティス」を持たせ、コミュニティに投票してもらいます。
私はしばらくの間 Ruby を使用してきましたが、大きなプロジェクトの場合、かなりの量のメモリを占有する可能性があることがわかりました。Ruby でメモリ使用量を削減するためのベスト プラクティスは何ですか?
ActiveRecord オブジェクトの巨大な配列を操作するときは、非常に注意してください...これらのオブジェクトをループで処理するとき、反復ごとに ActiveRecord の has_many、belongs_to などを使用して関連するオブジェクトをロードしている場合 - メモリ使用量が大幅に増加します。成長する配列に属しています...
次の手法は非常に役立ちました (単純化された例):
students.each do |student|
cloned_student = student.clone
...
cloned_student.books.detect {...}
ca_teachers = cloned_student.teachers.detect {|teacher| teacher.address.state == 'CA'}
ca_teachers.blah_blah
...
# Not sure if the following is necessary, but we have it just in case...
cloned_student = nil
end
上記のコードでは、「cloned_student」は成長するオブジェクトですが、各反復の最後に「無効化」されるため、学生の巨大な配列では問題になりません。「複製」を行わなかった場合、ループ変数「student」は大きくなりますが、これは配列に属しているため、配列オブジェクトが存在する限り、それによって使用されるメモリは決して解放されません。
別のアプローチも機能します。
students.each do |student|
loop_student = Student.find(student.id) # just re-find the record into local variable.
...
loop_student.books.detect {...}
ca_teachers = loop_student.teachers.detect {|teacher| teacher.address.state == 'CA'}
ca_teachers.blah_blah
...
end
私たちの実稼働環境では、8Gb の RAM では不十分であったため、一度終了に失敗したバックグラウンド プロセスがありました。この小さな変更の後、同じ量のデータを処理するために 1Gb 未満を使用します...
シンボルを乱用しないでください。
シンボルを作成するたびに、Ruby はそのシンボル テーブルにエントリを追加します。シンボル テーブルは、決して空にならないグローバル ハッシュです。
これは技術的にはメモリ リークではありませんが、メモリ リークのように動作します。シンボルは多くのメモリを消費しないので、過度に偏執的である必要はありませんが、これを認識しておくことは有益です。
一般的なガイドライン: コードで実際にシンボルを入力した場合は問題ありません (結局のところ、コードの量は限られています)。しかし、動的に生成された文字列またはユーザー入力の文字列に対して to_sym を呼び出さないでください。潜在的に増え続ける数に
これを行わないでください:
def method(x)
x.split( doesn't matter what the args are )
end
またはこれ:
def method(x)
x.gsub( doesn't matter what the args are )
end
どちらもルビー1.8.5と1.8.6のメモリを永久にリークします。(1.8.7については試したことがないのでわかりませんが、修正されることを願っています。)回避策は愚かで、ローカル変数を作成する必要があります。ローカルを使用する必要はありません。作成するだけです...
このようなことが、私がRuby言語をとても愛している理由ですが、MRIを尊重していません。
メモリの大きなチャンク自体を割り当てる C 拡張機能に注意してください。
例として、RMagick を使用してイメージをロードすると、ビットマップ全体が ruby プロセス内のメモリにロードされます。これは、画像のサイズによっては 30 メガ程度になる場合があります。
ただし、このメモリのほとんどは RMagick 自体によって割り当てられています。Ruby が知っているのは、tiny(1) であるラッパー オブジェクトだけです。
Ruby は、わずかな量のメモリを保持しているだけだと考えているため、GC を実行する必要はありません。実際には、30メガを保持しています。
たとえば 10 枚の画像をループすると、非常に速くメモリ不足になる可能性があります。
推奨される解決策は、手動で C ライブラリにメモリ自体をクリーンアップするよう指示することです。RMagick には destroy があります。これを行うメソッド。ただし、ライブラリがそうでない場合は、GC を自分で強制的に実行する必要があるかもしれませんが、これは一般的に推奨されません。
(1): Ruby C 拡張機能には、Ruby ランタイムがそれらを解放することを決定したときに実行されるコールバックがあります。