1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

[Feature #18901] Support size pool movement for Arrays

This commit enables Arrays to move between size pools during compaction.
This can occur if the array is mutated such that it would fit in a
different size pool when embedded.

The move is carried out in two stages:

1. The RVALUE is moved to a destination heap during object movement
   phase of compaction
2. The array data is re-embedded and the original buffer free'd if
   required. This happens during the update references step
This commit is contained in:
Matt Valentine-House 2022-06-09 15:59:08 +01:00 committed by Peter Zhu
parent 0f8a0c5f37
commit 214ed4cbc6
Notes: git 2022-07-12 21:51:02 +09:00
4 changed files with 102 additions and 8 deletions

42
array.c
View file

@ -107,7 +107,6 @@ should_not_be_shared_and_embedded(VALUE ary)
#define ARY_SET_EMBED_LEN(ary, n) do { \
long tmp_n = (n); \
assert(ARY_EMBED_P(ary)); \
assert(!OBJ_FROZEN(ary)); \
RBASIC(ary)->flags &= ~RARRAY_EMBED_LEN_MASK; \
RBASIC(ary)->flags |= (tmp_n) << RARRAY_EMBED_LEN_SHIFT; \
} while (0)
@ -212,6 +211,30 @@ ary_embeddable_p(long capa)
#endif
}
bool
rb_ary_embeddable_p(VALUE ary)
{
// if the array is shared or a shared root then it's not moveable
return !(ARY_SHARED_P(ary) || ARY_SHARED_ROOT_P(ary));
}
size_t
rb_ary_size_as_embedded(VALUE ary)
{
size_t real_size;
if (ARY_EMBED_P(ary)) {
real_size = ary_embed_size(ARY_EMBED_LEN(ary));
}
else if (rb_ary_embeddable_p(ary)) {
real_size = ary_embed_size(ARY_HEAP_CAPA(ary));
}
else {
real_size = sizeof(struct RString);
}
return real_size;
}
#if ARRAY_DEBUG
#define ary_verify(ary) ary_verify_(ary, __FILE__, __LINE__)
@ -468,6 +491,23 @@ rb_ary_detransient(VALUE ary)
}
#endif
void
rb_ary_make_embedded(VALUE ary)
{
assert(rb_ary_embeddable_p(ary));
if (!ARY_EMBED_P(ary)) {
VALUE *buf = RARRAY_PTR(ary);
long len = RARRAY_LEN(ary);
FL_SET_EMBED(ary);
ARY_SET_EMBED_LEN(ary, len);
RARY_TRANSIENT_UNSET(ary);
memmove(RARRAY_PTR(ary), buf, len * sizeof(VALUE));
ary_heap_free_ptr(ary, buf, len * sizeof(VALUE));
}
}
static void
ary_resize_capa(VALUE ary, long capacity)
{

25
gc.c
View file

@ -8324,16 +8324,20 @@ gc_compact_destination_pool(rb_objspace_t *objspace, rb_size_pool_t *src_pool, V
switch (BUILTIN_TYPE(src)) {
case T_STRING:
obj_size = rb_str_size_as_embedded(src);
if (rb_gc_size_allocatable_p(obj_size)){
return &size_pools[size_pool_idx_for_size(obj_size)];
}
else {
GC_ASSERT(!STR_EMBED_P(src));
return &size_pools[0];
}
break;
case T_ARRAY:
obj_size = rb_ary_size_as_embedded(src);
break;
default:
return src_pool;
}
if (rb_gc_size_allocatable_p(obj_size)) {
return &size_pools[size_pool_idx_for_size(obj_size)];
}
else {
return &size_pools[0];
}
}
static bool
@ -10368,6 +10372,13 @@ gc_update_object_references(rb_objspace_t *objspace, VALUE obj)
else {
gc_ref_update_array(objspace, obj);
}
#if USE_RVARGC
if ((size_t)GET_HEAP_PAGE(obj)->slot_size >= rb_ary_size_as_embedded(obj)) {
if (rb_ary_embeddable_p(obj)) {
rb_ary_make_embedded(obj);
}
}
#endif
break;
case T_HASH:

View file

@ -30,6 +30,9 @@ size_t rb_ary_memsize(VALUE);
VALUE rb_to_array_type(VALUE obj);
VALUE rb_to_array(VALUE obj);
void rb_ary_cancel_sharing(VALUE ary);
size_t rb_ary_size_as_embedded(VALUE ary);
void rb_ary_make_embedded(VALUE ary);
bool rb_ary_embeddable_p(VALUE ary);
static inline VALUE rb_ary_entry_internal(VALUE ary, long offset);
static inline bool ARY_PTR_USING_P(VALUE ary);

View file

@ -209,6 +209,46 @@ class TestGCCompact < Test::Unit::TestCase
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_strings_up_size_pools
assert_separately([], "#{<<~"begin;"}\n#{<<~"end;"}", timeout: 10, signal: :SEGV)
begin;