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:
Koichi Sasada 2023-07-31 16:17:55 +09:00
parent 280419d0e0
commit cfd7729ce7
Notes: git 2023-07-31 08:14:03 +00:00
7 changed files with 58 additions and 19 deletions

2
eval.c
View File

@ -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
View File

@ -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);

View File

@ -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 */

View File

@ -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)

View File

@ -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 | \

View File

@ -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);
} }

View File

@ -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();
} }