Optimize callcache invalidation for refinements
Fixes [Bug #21201] This change addresses a performance regression where defining methods inside `refine` blocks caused severe slowdowns. The issue was due to `rb_clear_all_refinement_method_cache()` triggering a full object space scan via `rb_objspace_each_objects` to find and invalidate affected callcaches, which is very inefficient. To fix this, I introduce `vm->cc_refinement_table` to track callcaches related to refinements. This allows us to invalidate only the necessary callcaches without scanning the entire heap, resulting in significant performance improvement.
This commit is contained in:
parent
d0b5f31554
commit
c8ddc0a843
Notes:
git
2025-06-09 03:33:51 +00:00
39
gc.c
39
gc.c
@ -2095,6 +2095,15 @@ rb_gc_obj_free_vm_weak_references(VALUE obj)
|
||||
break;
|
||||
case T_IMEMO:
|
||||
switch (imemo_type(obj)) {
|
||||
case imemo_callcache: {
|
||||
const struct rb_callcache *cc = (const struct rb_callcache *)obj;
|
||||
|
||||
if (vm_cc_refinement_p(cc)) {
|
||||
rb_vm_delete_cc_refinement(cc);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case imemo_callinfo:
|
||||
rb_vm_ci_free((const struct rb_callinfo *)obj);
|
||||
break;
|
||||
@ -3929,6 +3938,23 @@ vm_weak_table_foreach_update_weak_key(st_data_t *key, st_data_t *value, st_data_
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
vm_weak_table_cc_refinement_foreach(st_data_t key, st_data_t data, int error)
|
||||
{
|
||||
struct global_vm_table_foreach_data *iter_data = (struct global_vm_table_foreach_data *)data;
|
||||
|
||||
return iter_data->callback((VALUE)key, iter_data->data);
|
||||
}
|
||||
|
||||
static int
|
||||
vm_weak_table_cc_refinement_foreach_update_update(st_data_t *key, st_data_t data, int existing)
|
||||
{
|
||||
struct global_vm_table_foreach_data *iter_data = (struct global_vm_table_foreach_data *)data;
|
||||
|
||||
return iter_data->update_callback((VALUE *)key, iter_data->data);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
vm_weak_table_str_sym_foreach(st_data_t key, st_data_t value, st_data_t data, int error)
|
||||
{
|
||||
@ -4178,8 +4204,21 @@ rb_gc_vm_weak_table_foreach(vm_table_foreach_callback_func callback,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case RB_GC_VM_CC_REFINEMENT_TABLE: {
|
||||
if (vm->cc_refinement_table) {
|
||||
set_foreach_with_replace(
|
||||
vm->cc_refinement_table,
|
||||
vm_weak_table_cc_refinement_foreach,
|
||||
vm_weak_table_cc_refinement_foreach_update_update,
|
||||
(st_data_t)&foreach_data
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RB_GC_VM_WEAK_TABLE_COUNT:
|
||||
rb_bug("Unreacheable");
|
||||
default:
|
||||
rb_bug("rb_gc_vm_weak_table_foreach: unknown table %d", table);
|
||||
}
|
||||
}
|
||||
|
||||
|
1
gc/gc.h
1
gc/gc.h
@ -31,6 +31,7 @@ enum rb_gc_vm_weak_tables {
|
||||
RB_GC_VM_ID2REF_TABLE,
|
||||
RB_GC_VM_GENERIC_FIELDS_TABLE,
|
||||
RB_GC_VM_FROZEN_STRINGS_TABLE,
|
||||
RB_GC_VM_CC_REFINEMENT_TABLE,
|
||||
RB_GC_VM_WEAK_TABLE_COUNT
|
||||
};
|
||||
|
||||
|
@ -37,6 +37,8 @@ size_t rb_set_table_size(const struct set_table *tbl);
|
||||
set_table *rb_set_init_table_with_size(set_table *tab, const struct st_hash_type *, st_index_t);
|
||||
#define set_init_numtable rb_set_init_numtable
|
||||
set_table *rb_set_init_numtable(void);
|
||||
#define set_init_numtable_with_size rb_set_init_numtable_with_size
|
||||
set_table *rb_set_init_numtable_with_size(st_index_t size);
|
||||
#define set_delete rb_set_delete
|
||||
int rb_set_delete(set_table *, st_data_t *); /* returns 0:notfound 1:deleted */
|
||||
#define set_insert rb_set_insert
|
||||
|
3
method.h
3
method.h
@ -254,6 +254,9 @@ void rb_scope_visibility_set(rb_method_visibility_t);
|
||||
|
||||
VALUE rb_unnamed_parameters(int arity);
|
||||
|
||||
void rb_vm_insert_cc_refinement(const struct rb_callcache *cc);
|
||||
void rb_vm_delete_cc_refinement(const struct rb_callcache *cc);
|
||||
|
||||
void rb_clear_method_cache(VALUE klass_or_module, ID mid);
|
||||
void rb_clear_all_refinement_method_cache(void);
|
||||
void rb_invalidate_method_caches(struct rb_id_table *cm_tbl, struct rb_id_table *cc_tbl);
|
||||
|
6
st.c
6
st.c
@ -2465,6 +2465,12 @@ set_init_numtable(void)
|
||||
return set_init_table_with_size(NULL, &type_numhash, 0);
|
||||
}
|
||||
|
||||
set_table *
|
||||
set_init_numtable_with_size(st_index_t size)
|
||||
{
|
||||
return set_init_table_with_size(NULL, &type_numhash, size);
|
||||
}
|
||||
|
||||
size_t
|
||||
set_table_size(const struct set_table *tbl)
|
||||
{
|
||||
|
6
vm.c
6
vm.c
@ -3194,6 +3194,10 @@ ruby_vm_destruct(rb_vm_t *vm)
|
||||
st_free_table(vm->ci_table);
|
||||
vm->ci_table = NULL;
|
||||
}
|
||||
if (vm->cc_refinement_table) {
|
||||
rb_set_free_table(vm->cc_refinement_table);
|
||||
vm->cc_refinement_table = NULL;
|
||||
}
|
||||
RB_ALTSTACK_FREE(vm->main_altstack);
|
||||
|
||||
struct global_object_list *next;
|
||||
@ -3294,6 +3298,7 @@ vm_memsize(const void *ptr)
|
||||
vm_memsize_builtin_function_table(vm->builtin_function_table) +
|
||||
rb_id_table_memsize(vm->negative_cme_table) +
|
||||
rb_st_memsize(vm->overloaded_cme_table) +
|
||||
rb_set_memsize(vm->cc_refinement_table) +
|
||||
vm_memsize_constant_cache()
|
||||
);
|
||||
|
||||
@ -4503,6 +4508,7 @@ Init_vm_objects(void)
|
||||
vm->mark_object_ary = pin_array_list_new(Qnil);
|
||||
vm->loading_table = st_init_strtable();
|
||||
vm->ci_table = st_init_table(&vm_ci_hashtype);
|
||||
vm->cc_refinement_table = rb_set_init_numtable();
|
||||
}
|
||||
|
||||
// Stub for builtin function when not building YJIT units
|
||||
|
@ -345,6 +345,7 @@ vm_cc_new(VALUE klass,
|
||||
break;
|
||||
case cc_type_refinement:
|
||||
*(VALUE *)&cc->flags |= VM_CALLCACHE_REFINEMENT;
|
||||
rb_vm_insert_cc_refinement(cc);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -803,6 +803,7 @@ typedef struct rb_vm_struct {
|
||||
struct rb_id_table *negative_cme_table;
|
||||
st_table *overloaded_cme_table; // cme -> overloaded_cme
|
||||
set_table *unused_block_warning_table;
|
||||
set_table *cc_refinement_table;
|
||||
|
||||
// This id table contains a mapping from ID to ICs. It does this with ID
|
||||
// keys and nested st_tables as values. The nested tables have ICs as keys
|
||||
|
55
vm_method.c
55
vm_method.c
@ -393,27 +393,29 @@ rb_invalidate_method_caches(struct rb_id_table *cm_tbl, struct rb_id_table *cc_t
|
||||
}
|
||||
|
||||
static int
|
||||
invalidate_all_refinement_cc(void *vstart, void *vend, size_t stride, void *data)
|
||||
invalidate_cc_refinement(st_data_t key, st_data_t data)
|
||||
{
|
||||
VALUE v = (VALUE)vstart;
|
||||
for (; v != (VALUE)vend; v += stride) {
|
||||
VALUE v = (VALUE)key;
|
||||
void *ptr = rb_asan_poisoned_object_p(v);
|
||||
rb_asan_unpoison_object(v, false);
|
||||
|
||||
if (RBASIC(v)->flags) { // liveness check
|
||||
if (imemo_type_p(v, imemo_callcache)) {
|
||||
if (rb_gc_pointer_to_heap_p(v) &&
|
||||
!rb_objspace_garbage_object_p(v) &&
|
||||
RBASIC(v)->flags) { // liveness check
|
||||
const struct rb_callcache *cc = (const struct rb_callcache *)v;
|
||||
if (vm_cc_refinement_p(cc) && cc->klass) {
|
||||
|
||||
VM_ASSERT(vm_cc_refinement_p(cc));
|
||||
|
||||
if (cc->klass) {
|
||||
vm_cc_invalidate(cc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ptr) {
|
||||
rb_asan_poison_object(v);
|
||||
}
|
||||
}
|
||||
return 0; // continue to iteration
|
||||
|
||||
return ST_CONTINUE;
|
||||
}
|
||||
|
||||
static st_index_t
|
||||
@ -525,10 +527,43 @@ rb_vm_ci_free(const struct rb_callinfo *ci)
|
||||
st_delete(vm->ci_table, &key, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
rb_vm_insert_cc_refinement(const struct rb_callcache *cc)
|
||||
{
|
||||
st_data_t key = (st_data_t)cc;
|
||||
|
||||
rb_vm_t *vm = GET_VM();
|
||||
RB_VM_LOCK_ENTER();
|
||||
{
|
||||
rb_set_insert(vm->cc_refinement_table, key);
|
||||
}
|
||||
RB_VM_LOCK_LEAVE();
|
||||
}
|
||||
|
||||
void
|
||||
rb_vm_delete_cc_refinement(const struct rb_callcache *cc)
|
||||
{
|
||||
ASSERT_vm_locking();
|
||||
|
||||
rb_vm_t *vm = GET_VM();
|
||||
st_data_t key = (st_data_t)cc;
|
||||
|
||||
rb_set_delete(vm->cc_refinement_table, &key);
|
||||
}
|
||||
|
||||
void
|
||||
rb_clear_all_refinement_method_cache(void)
|
||||
{
|
||||
rb_objspace_each_objects(invalidate_all_refinement_cc, NULL);
|
||||
rb_vm_t *vm = GET_VM();
|
||||
|
||||
RB_VM_LOCK_ENTER();
|
||||
{
|
||||
rb_set_foreach(vm->cc_refinement_table, invalidate_cc_refinement, (st_data_t)NULL);
|
||||
rb_set_clear(vm->cc_refinement_table);
|
||||
rb_set_compact_table(vm->cc_refinement_table);
|
||||
}
|
||||
RB_VM_LOCK_LEAVE();
|
||||
|
||||
rb_yjit_invalidate_all_method_lookup_assumptions();
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user