1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/test/ruby/test_gc_compact.rb
Peter Zhu 7424ea184f Implement Objects on VWA
This commit implements Objects on Variable Width Allocation. This allows
Objects with more ivars to be embedded (i.e. contents directly follow the
object header) which improves performance through better cache locality.
2022-07-15 09:21:07 -04:00

310 lines
8.2 KiB
Ruby

# frozen_string_literal: true
require 'test/unit'
require 'fiddle'
require 'etc'
if RUBY_PLATFORM =~ /s390x/
warn "Currently, it is known that the compaction does not work well on s390x; contribution is welcome https://github.com/ruby/ruby/pull/5077"
return
end
class TestGCCompact < Test::Unit::TestCase
module CompactionSupportInspector
def supports_auto_compact?
GC::OPTS.include?("GC_COMPACTION_SUPPORTED")
end
end
module OmitUnlessCompactSupported
include CompactionSupportInspector
def setup
omit "autocompact not supported on this platform" unless supports_auto_compact?
super
end
end
include OmitUnlessCompactSupported
class AutoCompact < Test::Unit::TestCase
include OmitUnlessCompactSupported
def test_enable_autocompact
before = GC.auto_compact
GC.auto_compact = true
assert GC.auto_compact
ensure
GC.auto_compact = before
end
def test_disable_autocompact
before = GC.auto_compact
GC.auto_compact = false
refute GC.auto_compact
ensure
GC.auto_compact = before
end
def test_major_compacts
before = GC.auto_compact
GC.auto_compact = true
compact = GC.stat :compact_count
GC.start
assert_operator GC.stat(:compact_count), :>, compact
ensure
GC.auto_compact = before
end
def test_implicit_compaction_does_something
before = GC.auto_compact
list = []
list2 = []
# Try to make some fragmentation
500.times {
list << Object.new
Object.new
Object.new
}
count = GC.stat :compact_count
GC.auto_compact = true
n = 1_000_000
n.times do
break if count < GC.stat(:compact_count)
list2 << Object.new
end and omit "implicit compaction didn't happen within #{n} objects"
compact_stats = GC.latest_compact_info
refute_predicate compact_stats[:considered], :empty?
refute_predicate compact_stats[:moved], :empty?
ensure
GC.auto_compact = before
end
end
class CompactMethodsNotImplemented < Test::Unit::TestCase
include CompactionSupportInspector
def assert_not_implemented(method, *args)
omit "autocompact is supported on this platform" if supports_auto_compact?
assert_raise(NotImplementedError) { GC.send(method, *args) }
refute(GC.respond_to?(method), "GC.#{method} should be defined as rb_f_notimplement")
end
def test_gc_compact_not_implemented
assert_not_implemented(:compact)
end
def test_gc_auto_compact_get_not_implemented
assert_not_implemented(:auto_compact)
end
def test_gc_auto_compact_set_not_implemented
assert_not_implemented(:auto_compact=, true)
end
def test_gc_latest_compact_info_not_implemented
assert_not_implemented(:latest_compact_info)
end
def test_gc_verify_compaction_references_not_implemented
assert_not_implemented(:verify_compaction_references)
end
end
def os_page_size
return true unless defined?(Etc::SC_PAGE_SIZE)
end
def test_gc_compact_stats
list = []
# Try to make some fragmentation
500.times {
list << Object.new
Object.new
Object.new
}
compact_stats = GC.compact
refute_predicate compact_stats[:considered], :empty?
refute_predicate compact_stats[:moved], :empty?
end
def memory_location(obj)
(Fiddle.dlwrap(obj) >> 1)
end
def big_list(level = 10)
if level > 0
big_list(level - 1)
else
1000.times.map {
# try to make some empty slots by allocating an object and discarding
Object.new
Object.new
} # likely next to each other
end
end
# Find an object that's allocated in a slot that had a previous
# tenant, and that tenant moved and is still alive
def find_object_in_recycled_slot(addresses)
new_object = nil
100_000.times do
new_object = Object.new
if addresses.index memory_location(new_object)
break
end
end
new_object
end
def test_complex_hash_keys
list_of_objects = big_list
hash = list_of_objects.hash
GC.verify_compaction_references(toward: :empty)
assert_equal hash, list_of_objects.hash
GC.verify_compaction_references(expand_heap: false)
assert_equal hash, list_of_objects.hash
end
def test_ast_compacts
assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
begin;
def walk_ast ast
children = ast.children.grep(RubyVM::AbstractSyntaxTree::Node)
children.each do |child|
assert child.type
walk_ast child
end
end
ast = RubyVM::AbstractSyntaxTree.parse_file #{__FILE__.dump}
assert GC.compact
walk_ast ast
end;
end
def test_compact_count
count = GC.stat(:compact_count)
GC.compact
assert_equal count + 1, GC.stat(:compact_count)
end
def test_compacting_from_trace_point
obj = Object.new
def obj.tracee
:ret # expected to emit both line and call event from one instruction
end
results = []
TracePoint.new(:call, :line) do |tp|
results << tp.event
GC.verify_compaction_references
end.enable(target: obj.method(:tracee)) do
obj.tracee
end
assert_equal([:call, :line], results)
end
def test_moving_arrays_down_size_pools
omit if !GC.using_rvargc?
assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
begin;
ARY_COUNT = 500
GC.verify_compaction_references(expand_heap: true, toward: :empty)
arys = ARY_COUNT.times.map do
ary = "abbbbbbbbbb".chars
ary.uniq!
end
stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
assert_operator(stats.dig(:moved_down, :T_ARRAY), :>=, ARY_COUNT)
assert(arys) # warning: assigned but unused variable - arys
end;
end
def test_moving_arrays_up_size_pools
omit if !GC.using_rvargc?
assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
begin;
ARY_COUNT = 500
GC.verify_compaction_references(expand_heap: true, toward: :empty)
ary = "hello".chars
arys = ARY_COUNT.times.map do
x = []
ary.each { |e| x << e }
x
end
stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
assert_operator(stats.dig(:moved_up, :T_ARRAY), :>=, ARY_COUNT)
assert(arys) # warning: assigned but unused variable - arys
end;
end
def test_moving_objects_between_size_pools
assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
begin;
class Foo
def add_ivars
10.times do |i|
instance_variable_set("@foo" + i.to_s, 0)
end
end
end
OBJ_COUNT = 500
GC.verify_compaction_references(expand_heap: true, toward: :empty)
ary = OBJ_COUNT.times.map { Foo.new }
ary.each(&:add_ivars)
stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
assert_operator(stats[:moved_up][:T_OBJECT], :>=, OBJ_COUNT)
end;
end
def test_moving_strings_up_size_pools
omit if !GC.using_rvargc?
assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
begin;
STR_COUNT = 500
GC.verify_compaction_references(expand_heap: true, toward: :empty)
str = "a" * GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE]
ary = STR_COUNT.times.map { "" << str }
stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
assert_operator(stats[:moved_up][:T_STRING], :>=, STR_COUNT)
assert(ary) # warning: assigned but unused variable - ary
end;
end
def test_moving_strings_down_size_pools
omit if !GC.using_rvargc?
assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
begin;
STR_COUNT = 500
GC.verify_compaction_references(expand_heap: true, toward: :empty)
ary = STR_COUNT.times.map { ("a" * GC::INTERNAL_CONSTANTS[:BASE_SLOT_SIZE]).squeeze! }
stats = GC.verify_compaction_references(expand_heap: true, toward: :empty)
assert_operator(stats[:moved_down][:T_STRING], :>=, STR_COUNT)
assert(ary) # warning: assigned but unused variable - ary
end;
end
end