rubyのyieldでGCってどうなるのかちょっと気になったので調べてみました。
コード
gc.rb
def opener1 Array.new(3).each do |_e| a = Array.new(1_000_000) do 'hello' end yield a end end def opener2 Array.new(3).map do |_e| Array.new(1_000_000) do 'hello' end end end def check_gc(p) p.length GC.start puts "did gc: #{GC.stat.slice(:minor_gc_count, :major_gc_count, :heap_free_slots, :heap_live_slots, :old_objects)}" end GC::Profiler.enable puts "start: #{GC.stat.slice(:minor_gc_count, :major_gc_count, :heap_free_slots, :heap_live_slots, :old_objects)}" # opener1 do |p| # check_gc(p) # end opener2.each do |p| check_gc(p) end puts "total_time: #{GC::Profiler.total_time.round(3)}ms"
結果
yieldで返すopener1の場合、loopごとにheap free slotが増えold objectが消えてるので使い終わったらGCされていそうです。
$ ruby gc.rb start: {:minor_gc_count=>9, :major_gc_count=>2, :heap_free_slots=>70, :heap_live_slots=>23163, :old_objects=>14939} did gc: {:minor_gc_count=>17, :major_gc_count=>6, :heap_free_slots=>229, :heap_live_slots=>1015920, :old_objects=>1015702} did gc: {:minor_gc_count=>18, :major_gc_count=>7, :heap_free_slots=>1000068, :heap_live_slots=>1015922, :old_objects=>15702} did gc: {:minor_gc_count=>18, :major_gc_count=>8, :heap_free_slots=>1000067, :heap_live_slots=>1015923, :old_objects=>15702} total_time: 0.149ms
通常のArrayを返すopener2では、配列を生成してそれを読み込んでいるので、GCされずにold_objectとして保持されています。
$ ruby gc.rb start: {:minor_gc_count=>9, :major_gc_count=>2, :heap_free_slots=>77, :heap_live_slots=>23155, :old_objects=>14940} did gc: {:minor_gc_count=>19, :major_gc_count=>6, :heap_free_slots=>725, :heap_live_slots=>3015923, :old_objects=>3015705} did gc: {:minor_gc_count=>19, :major_gc_count=>7, :heap_free_slots=>724, :heap_live_slots=>3015924, :old_objects=>3015705} did gc: {:minor_gc_count=>19, :major_gc_count=>8, :heap_free_slots=>723, :heap_live_slots=>3015925, :old_objects=>3015705} total_time: 0.343ms