use inline cache for refinements
From Ruby 3.0, refined method invocations are slow because resolved methods are not cached by inline cache because of conservertive strategy. However, `using` clears all caches so that it seems safe to cache resolved method entries. This patch caches resolved method entries in inline cache and clear all of inline method caches when `using` is called. fix [Bug #18572] ```ruby # without refinements class C def foo = :C end N = 1_000_000 obj = C.new require 'benchmark' Benchmark.bm{|x| x.report{N.times{ obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; }} } _END__ user system total real master 0.362859 0.002544 0.365403 ( 0.365424) modified 0.357251 0.000000 0.357251 ( 0.357258) ``` ```ruby # with refinment but without using class C def foo = :C end module R refine C do def foo = :R end end N = 1_000_000 obj = C.new require 'benchmark' Benchmark.bm{|x| x.report{N.times{ obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; }} } __END__ user system total real master 0.957182 0.000000 0.957182 ( 0.957212) modified 0.359228 0.000000 0.359228 ( 0.359238) ``` ```ruby # with using class C def foo = :C end module R refine C do def foo = :R end end N = 1_000_000 using R obj = C.new require 'benchmark' Benchmark.bm{|x| x.report{N.times{ obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; obj.foo; }} }
This commit is contained in:
parent
280419d0e0
commit
cfd7729ce7
Notes:
git
2023-07-31 08:14:03 +00:00
2
eval.c
2
eval.c
@ -1341,7 +1341,7 @@ rb_using_module(const rb_cref_t *cref, VALUE module)
|
|||||||
{
|
{
|
||||||
Check_Type(module, T_MODULE);
|
Check_Type(module, T_MODULE);
|
||||||
using_module_recursive(cref, module);
|
using_module_recursive(cref, module);
|
||||||
rb_clear_method_cache_all();
|
rb_clear_all_refinement_method_cache();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
2
gc.c
2
gc.c
@ -3187,6 +3187,8 @@ vm_ccs_free(struct rb_class_cc_entries *ccs, int alive, rb_objspace_t *objspace,
|
|||||||
asan_poison_object((VALUE)cc);
|
asan_poison_object((VALUE)cc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VM_ASSERT(!vm_cc_super_p(cc) && !vm_cc_refinement_p(cc));
|
||||||
vm_cc_invalidate(cc);
|
vm_cc_invalidate(cc);
|
||||||
}
|
}
|
||||||
ruby_xfree(ccs->entries);
|
ruby_xfree(ccs->entries);
|
||||||
|
2
method.h
2
method.h
@ -249,6 +249,6 @@ void rb_scope_visibility_set(rb_method_visibility_t);
|
|||||||
VALUE rb_unnamed_parameters(int arity);
|
VALUE rb_unnamed_parameters(int arity);
|
||||||
|
|
||||||
void rb_clear_method_cache(VALUE klass_or_module, ID mid);
|
void rb_clear_method_cache(VALUE klass_or_module, ID mid);
|
||||||
void rb_clear_method_cache_all(void);
|
void rb_clear_all_refinement_method_cache(void);
|
||||||
|
|
||||||
#endif /* RUBY_METHOD_H */
|
#endif /* RUBY_METHOD_H */
|
||||||
|
@ -2626,6 +2626,24 @@ class TestRefinement < Test::Unit::TestCase
|
|||||||
assert_equal([], Refinement.used_modules)
|
assert_equal([], Refinement.used_modules)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_inlinecache
|
||||||
|
assert_separately([], <<-"end;")
|
||||||
|
module R
|
||||||
|
refine String do
|
||||||
|
def to_s = :R
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
2.times{|i|
|
||||||
|
s = ''.to_s
|
||||||
|
assert_equal '', s if i == 0
|
||||||
|
assert_equal :R, s if i == 1
|
||||||
|
using R if i == 0
|
||||||
|
assert_equal :R, ''.to_s
|
||||||
|
}
|
||||||
|
end;
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def eval_using(mod, s)
|
def eval_using(mod, s)
|
||||||
|
@ -299,10 +299,12 @@ struct rb_callcache {
|
|||||||
#define VM_CALLCACHE_IVAR IMEMO_FL_USER0
|
#define VM_CALLCACHE_IVAR IMEMO_FL_USER0
|
||||||
#define VM_CALLCACHE_BF IMEMO_FL_USER1
|
#define VM_CALLCACHE_BF IMEMO_FL_USER1
|
||||||
#define VM_CALLCACHE_SUPER IMEMO_FL_USER2
|
#define VM_CALLCACHE_SUPER IMEMO_FL_USER2
|
||||||
|
#define VM_CALLCACHE_REFINEMENT IMEMO_FL_USER3
|
||||||
|
|
||||||
enum vm_cc_type {
|
enum vm_cc_type {
|
||||||
cc_type_normal, // chained from ccs
|
cc_type_normal, // chained from ccs
|
||||||
cc_type_super,
|
cc_type_super,
|
||||||
|
cc_type_refinement,
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const struct rb_callcache *rb_vm_empty_cc(void);
|
extern const struct rb_callcache *rb_vm_empty_cc(void);
|
||||||
@ -332,6 +334,9 @@ vm_cc_new(VALUE klass,
|
|||||||
case cc_type_super:
|
case cc_type_super:
|
||||||
*(VALUE *)&cc->flags |= VM_CALLCACHE_SUPER;
|
*(VALUE *)&cc->flags |= VM_CALLCACHE_SUPER;
|
||||||
break;
|
break;
|
||||||
|
case cc_type_refinement:
|
||||||
|
*(VALUE *)&cc->flags |= VM_CALLCACHE_REFINEMENT;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
vm_cc_attr_index_initialize(cc, INVALID_SHAPE_ID);
|
vm_cc_attr_index_initialize(cc, INVALID_SHAPE_ID);
|
||||||
@ -345,6 +350,12 @@ vm_cc_super_p(const struct rb_callcache *cc)
|
|||||||
return (cc->flags & VM_CALLCACHE_SUPER) != 0;
|
return (cc->flags & VM_CALLCACHE_SUPER) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
vm_cc_refinement_p(const struct rb_callcache *cc)
|
||||||
|
{
|
||||||
|
return (cc->flags & VM_CALLCACHE_REFINEMENT) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
#define VM_CC_ON_STACK(clazz, call, aux, cme) \
|
#define VM_CC_ON_STACK(clazz, call, aux, cme) \
|
||||||
(struct rb_callcache) { \
|
(struct rb_callcache) { \
|
||||||
.flags = T_IMEMO | \
|
.flags = T_IMEMO | \
|
||||||
|
@ -2003,6 +2003,8 @@ vm_ccs_verify(struct rb_class_cc_entries *ccs, ID mid, VALUE klass)
|
|||||||
VM_ASSERT(IMEMO_TYPE_P(cc, imemo_callcache));
|
VM_ASSERT(IMEMO_TYPE_P(cc, imemo_callcache));
|
||||||
VM_ASSERT(vm_cc_class_check(cc, klass));
|
VM_ASSERT(vm_cc_class_check(cc, klass));
|
||||||
VM_ASSERT(vm_cc_check_cme(cc, ccs->cme));
|
VM_ASSERT(vm_cc_check_cme(cc, ccs->cme));
|
||||||
|
VM_ASSERT(!vm_cc_super_p(cc));
|
||||||
|
VM_ASSERT(!vm_cc_refinement_p(cc));
|
||||||
}
|
}
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@ -4193,13 +4195,20 @@ search_refined_method(rb_execution_context_t *ec, rb_control_frame_t *cfp, struc
|
|||||||
static VALUE
|
static VALUE
|
||||||
vm_call_refined(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling)
|
vm_call_refined(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling)
|
||||||
{
|
{
|
||||||
struct rb_callcache *ref_cc = &VM_CC_ON_STACK(Qundef, vm_call_general, {{ 0 }},
|
const rb_callable_method_entry_t *ref_cme = search_refined_method(ec, cfp, calling);
|
||||||
search_refined_method(ec, cfp, calling));
|
|
||||||
|
|
||||||
if (vm_cc_cme(ref_cc)) {
|
if (ref_cme) {
|
||||||
|
if (calling->cd->cc) {
|
||||||
|
const struct rb_callcache *cc = calling->cc = vm_cc_new(vm_cc_cme(calling->cc)->defined_class, ref_cme, vm_call_general, cc_type_refinement);
|
||||||
|
RB_OBJ_WRITE(cfp->iseq, &calling->cd->cc, cc);
|
||||||
|
return vm_call_method(ec, cfp, calling);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
struct rb_callcache *ref_cc = &VM_CC_ON_STACK(Qundef, vm_call_general, {{ 0 }}, ref_cme);
|
||||||
calling->cc= ref_cc;
|
calling->cc= ref_cc;
|
||||||
return vm_call_method(ec, cfp, calling);
|
return vm_call_method(ec, cfp, calling);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
return vm_call_method_nome(ec, cfp, calling);
|
return vm_call_method_nome(ec, cfp, calling);
|
||||||
}
|
}
|
||||||
|
17
vm_method.c
17
vm_method.c
@ -307,19 +307,19 @@ rb_clear_method_cache(VALUE klass_or_module, ID mid)
|
|||||||
void rb_cc_table_free(VALUE klass);
|
void rb_cc_table_free(VALUE klass);
|
||||||
|
|
||||||
static int
|
static int
|
||||||
invalidate_all_cc(void *vstart, void *vend, size_t stride, void *data)
|
invalidate_all_refinement_cc(void *vstart, void *vend, size_t stride, void *data)
|
||||||
{
|
{
|
||||||
VALUE v = (VALUE)vstart;
|
VALUE v = (VALUE)vstart;
|
||||||
for (; v != (VALUE)vend; v += stride) {
|
for (; v != (VALUE)vend; v += stride) {
|
||||||
void *ptr = asan_poisoned_object_p(v);
|
void *ptr = asan_poisoned_object_p(v);
|
||||||
asan_unpoison_object(v, false);
|
asan_unpoison_object(v, false);
|
||||||
|
|
||||||
if (RBASIC(v)->flags) { // liveness check
|
if (RBASIC(v)->flags) { // liveness check
|
||||||
if (RB_TYPE_P(v, T_CLASS) ||
|
if (imemo_type_p(v, imemo_callcache)) {
|
||||||
RB_TYPE_P(v, T_ICLASS)) {
|
const struct rb_callcache *cc = (const struct rb_callcache *)v;
|
||||||
if (RCLASS_CC_TBL(v)) {
|
if (vm_cc_refinement_p(cc) && cc->klass) {
|
||||||
rb_cc_table_free(v);
|
vm_cc_invalidate(cc);
|
||||||
}
|
}
|
||||||
RCLASS_CC_TBL(v) = NULL;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,10 +331,9 @@ invalidate_all_cc(void *vstart, void *vend, size_t stride, void *data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
rb_clear_method_cache_all(void)
|
rb_clear_all_refinement_method_cache(void)
|
||||||
{
|
{
|
||||||
rb_objspace_each_objects(invalidate_all_cc, NULL);
|
rb_objspace_each_objects(invalidate_all_refinement_cc, NULL);
|
||||||
|
|
||||||
rb_yjit_invalidate_all_method_lookup_assumptions();
|
rb_yjit_invalidate_all_method_lookup_assumptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user