Last active
October 7, 2015 14:19
-
-
Save tdg5/0b9f145edb5114a2dca1 to your computer and use it in GitHub Desktop.
Example seeming to demonstrate that unary ampersand operator holds on to obj references in Ruby 2.1.x
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Create some special classes to facilitate tracking allocated objects. | |
class TrackedArray < Array; end | |
class TrackedString < String; end | |
STRANG = "a" * 5000 | |
class ClingyObjects | |
def generate(should_cling = false) | |
strs = TrackedArray.new | |
30000.times { strs << TrackedString.new(STRANG) } | |
char_count = 0 | |
# I'm not sure why, but using the unary & operator on the Array, whether | |
# through #each or #map, prevents the allocated objects from being GC'd. | |
# Maybe I'm missing something, but after this method returns nothing | |
# should refer to the strs Array or any of the objects contained in the | |
# Array, so GC should proceed without issue. What gives? | |
strs.each(&:length) if should_cling | |
strs.each {|x| char_count += x.length } | |
char_count | |
end | |
# Helper to print object allocation stats. | |
def object_stats(tag) | |
puts "#{tag}:" | |
puts "TrackedArray: #{ObjectSpace.each_object(TrackedArray).count}" | |
puts "TrackedString: #{ObjectSpace.each_object(TrackedString).count}" | |
end | |
def print_with_stats(char_count) | |
object_stats("Before GC") | |
# Run the garbage collector. | |
GC.start | |
object_stats("After GC") | |
puts char_count | |
end | |
end | |
def wrapper | |
clinger = ClingyObjects.new | |
puts "Non-clingy:" | |
count = clinger.generate | |
clinger.print_with_stats(count) | |
puts "\nClingy:" | |
count = clinger.generate(:should_cling) | |
clinger.print_with_stats(count) | |
# Try to GC again for fun | |
puts "\nTry GC again" | |
GC.start | |
clinger.print_with_stats(count) | |
puts "\nDitch clinger and try GC again" | |
clinger = nil | |
5.times do | |
GC.start | |
puts "TrackedArray: #{ObjectSpace.each_object(TrackedArray).count}" | |
puts "TrackedString: #{ObjectSpace.each_object(TrackedString).count}" | |
puts "\nSleep a bit and try again" | |
sleep 3 | |
end | |
puts "TrackedArray: #{ObjectSpace.each_object(TrackedArray).count}" | |
puts "TrackedString: #{ObjectSpace.each_object(TrackedString).count}" | |
end | |
wrapper | |
puts "TrackedArray: #{ObjectSpace.each_object(TrackedArray).count}" | |
puts "TrackedString: #{ObjectSpace.each_object(TrackedString).count}" | |
# Non-clingy: | |
# Before GC: | |
# TrackedArray: 1 | |
# TrackedString: 30000 | |
# After GC: | |
# TrackedArray: 0 | |
# TrackedString: 0 | |
# 150000000 | |
# Clingy: | |
# Before GC: | |
# TrackedArray: 1 | |
# TrackedString: 30000 | |
# After GC: | |
# TrackedArray: 1 | |
# TrackedString: 30000 | |
# 150000000 | |
# Try GC again | |
# Before GC: | |
# TrackedArray: 1 | |
# TrackedString: 30000 | |
# After GC: | |
# TrackedArray: 1 | |
# TrackedString: 30000 | |
# 150000000 | |
# Ditch clinger and try GC again | |
# TrackedArray: 1 | |
# TrackedString: 30000 | |
# Sleep a bit and try again | |
# TrackedArray: 1 | |
# TrackedString: 30000 | |
# Sleep a bit and try again | |
# TrackedArray: 1 | |
# TrackedString: 30000 | |
# Sleep a bit and try again | |
# TrackedArray: 1 | |
# TrackedString: 30000 | |
# Sleep a bit and try again | |
# TrackedArray: 1 | |
# TrackedString: 30000 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment