21

Ruby ヒープから割り当てられたメモリがいつオペレーティング システムに返されるかを理解しようとしています。Ruby がヒープに割り当てられたメモリを返さないことは理解していますが、オフ ヒープ メモリの動作についてはまだよくわかりません。つまり、40 バイトの RVALUE に収まらないオブジェクトです。

いくつかの大きな文字列を割り当ててからメジャー GC を強制する次のプログラムを考えてみましょう。

require 'objspace'

STRING_SIZE = 250

def print_stats(msg)
  puts '-------------------'
  puts msg
  puts '-------------------'
  puts "RSS: #{`ps -eo rss,pid | grep #{Process.pid} | grep -v grep | awk '{ print $1,"KB";}'`}"
  puts "HEAP SIZE: #{(GC.stat[:heap_sorted_length] * 408 * 40)/1024} KB"
  puts "SIZE OF ALL OBJECTS: #{ObjectSpace.memsize_of_all/1024} KB"
end

def run
  print_stats('START WORK')
  @data=[]
  600_000.times do
    @data <<  " "  * STRING_SIZE
  end
  print_stats('END WORK')
  @data=nil
end

run
GC.start
print_stats('AFTER FORCED MAJOR GC')

MRI で Ruby 2.2.3 を使用してこのプログラムを実行すると、次の出力が生成されます。強制メジャー GC の後、ヒープ サイズは予想どおりですが、RSS は大幅に減少していません。

-------------------
START WORK
-------------------
RSS: 7036 KB
HEAP SIZE: 1195 KB
SIZE OF ALL OBJECTS: 3172 KB
-------------------
END WORK
-------------------
RSS: 205660 KB
HEAP SIZE: 35046 KB
SIZE OF ALL OBJECTS: 178423 KB
-------------------
AFTER FORCED MAJOR GC
-------------------
RSS: 164492 KB
HEAP SIZE: 35046 KB
SIZE OF ALL OBJECTS: 2484 KB

これらの結果を、多数の小さなオブジェクトではなく 1 つの大きなオブジェクトを割り当てた場合の次の結果と比較してください。

def run
  print_stats('START WORK')
  @data = " " * STRING_SIZE * 600_000
  print_stats('END WORK')
  @data=nil
end

-------------------
START WORK
-------------------
RSS: 7072 KB
HEAP SIZE: 1195 KB
SIZE OF ALL OBJECTS: 3170 KB
-------------------
END WORK
-------------------
RSS: 153584 KB
HEAP SIZE: 1195 KB
SIZE OF ALL OBJECTS: 149064 KB
-------------------
AFTER FORCED MAJOR GC
-------------------
RSS: 7096 KB
HEAP SIZE: 1195 KB
SIZE OF ALL OBJECTS: 2483 KB

最終的な RSS 値に注意してください。大きな文字列に割り当てたすべてのメモリを解放したようです。

2 番目の例がメモリを解放する理由はわかりませんが、最初の例はどちらも Ruby ヒープからメモリを割り当てているため、そうではありません。これは説明を提供できる参考文献の 1 つですが、他の人からの説明に興味があります。

メモリを解放してカーネルに戻すことにもコストがかかります。ユーザー空間のメモリ アロケータは、同じプロセス内で再利用でき、他のプロセスで使用するためにカーネルに返さないことを期待して、そのメモリを (プライベートに) 保持する場合があります。

4

1 に答える 1

2

@joanbm has a very good point here. His referenced article explains this pretty well :

Ruby's GC releases memory gradually, so when you do GC on 1 big chunk of memory pointed by 1 reference it releases it all, but when there is a lot of references, the GC will releases memory in smaller chuncks.

Several calls to GC.start will release more and more memory in the 1st example.


Here are 2 orther articles to dig deeper :

于 2016-02-23T15:48:51.297 に答える