Created
October 7, 2015 12:55
-
-
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.
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 | |
# 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