Skip to content

Instantly share code, notes, and snippets.

@tdg5
Created October 7, 2015 12:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tdg5/6f316e3bdbb82deaf180 to your computer and use it in GitHub Desktop.
Save tdg5/6f316e3bdbb82deaf180 to your computer and use it in GitHub Desktop.
Example demonstrating that tail-call optimization in MRI Ruby allows for earlier garbage collection of unreachable objects.
# Create some special classes to facilitate tracking allocated objects.
class TrackedArray < Array; end
class TrackedString < String; end
STRANG = "a" * 5000
# Other than a few extra escapes, the code below can be understood more or less
# as is. Any weirdness is to facilitate interpretting with/without tail-call
# optimization in a DRY manner.
KLASS = <<-CODE
class GcFriendlyTco
def calculate
memory_hog
end
# Private because who knows what madness some libs are up to under the hood.
private
# Represents a method that does a bunch of memory intensive work before
# returning an aggregate or some other type of result with a comparatively
# small memory footprint.
def memory_hog
strs = TrackedArray.new
30000.times { strs << TrackedString.new(STRANG) }
char_count = 0
strs.length.times {|i| char_count += strs[i].length }
print_with_stats(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
# Printer method serving as both the end of the tail-call chain and as the
# provider of introspection.
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
CODE
# Evaluate with TCO if the TCO environment variable is defined.
if ENV["TCO"]
# Compilation with TCO requires the tco_method gem.
require "tco_method"
TCOMethod.tco_eval(KLASS)
else
eval(KLASS)
end
GcFriendlyTco.new.calculate
# $ ruby memory_efficient_tco.rb
# Before GC:
# TrackedArray: 1
# TrackedString: 30000
# After GC:
# TrackedArray: 1
# TrackedString: 30000
# 150000000
# $ TCO=true ruby memory_efficient_tco.rb
# Before GC:
# TrackedArray: 1
# TrackedString: 30000
# After GC:
# TrackedArray: 0
# TrackedString: 0
# 150000000
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment