Skip to content

Commit

Permalink
use inline cache for refinements
Browse files Browse the repository at this point in the history
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;
  }}
}
  • Loading branch information
ko1 committed Jul 31, 2023
1 parent ab81440 commit 48dccdb
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 19 deletions.
2 changes: 1 addition & 1 deletion eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -1341,7 +1341,7 @@ rb_using_module(const rb_cref_t *cref, VALUE module)
{
Check_Type(module, T_MODULE);
using_module_recursive(cref, module);
rb_clear_method_cache_all();
rb_clear_all_refinement_method_cache();
}

/*
Expand Down
2 changes: 2 additions & 0 deletions gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -3187,6 +3187,8 @@ vm_ccs_free(struct rb_class_cc_entries *ccs, int alive, rb_objspace_t *objspace,
asan_poison_object((VALUE)cc);
}
}

VM_ASSERT(!vm_cc_super_p(cc) && !vm_cc_refinement_p(cc));
vm_cc_invalidate(cc);
}
ruby_xfree(ccs->entries);
Expand Down
2 changes: 1 addition & 1 deletion method.h
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,6 @@ void rb_scope_visibility_set(rb_method_visibility_t);
VALUE rb_unnamed_parameters(int arity);

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 */
18 changes: 18 additions & 0 deletions test/ruby/test_refinement.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2626,6 +2626,24 @@ def test_inherit_singleton_methods_of_module
assert_equal([], Refinement.used_modules)
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

def eval_using(mod, s)
Expand Down
17 changes: 14 additions & 3 deletions vm_callinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -296,13 +296,15 @@ struct rb_callcache {
#define VM_CALLCACHE_UNMARKABLE FL_FREEZE
#define VM_CALLCACHE_ON_STACK FL_EXIVAR

#define VM_CALLCACHE_IVAR IMEMO_FL_USER0
#define VM_CALLCACHE_BF IMEMO_FL_USER1
#define VM_CALLCACHE_SUPER IMEMO_FL_USER2
#define VM_CALLCACHE_IVAR IMEMO_FL_USER0
#define VM_CALLCACHE_BF IMEMO_FL_USER1
#define VM_CALLCACHE_SUPER IMEMO_FL_USER2
#define VM_CALLCACHE_REFINEMENT IMEMO_FL_USER3

enum vm_cc_type {
cc_type_normal, // chained from ccs
cc_type_super,
cc_type_refinement,
};

extern const struct rb_callcache *rb_vm_empty_cc(void);
Expand Down Expand Up @@ -332,6 +334,9 @@ vm_cc_new(VALUE klass,
case cc_type_super:
*(VALUE *)&cc->flags |= VM_CALLCACHE_SUPER;
break;
case cc_type_refinement:
*(VALUE *)&cc->flags |= VM_CALLCACHE_REFINEMENT;
break;
}

vm_cc_attr_index_initialize(cc, INVALID_SHAPE_ID);
Expand All @@ -345,6 +350,12 @@ vm_cc_super_p(const struct rb_callcache *cc)
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) \
(struct rb_callcache) { \
.flags = T_IMEMO | \
Expand Down
19 changes: 14 additions & 5 deletions vm_insnhelper.c
Original file line number Diff line number Diff line change
Expand Up @@ -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(vm_cc_class_check(cc, klass));
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;
}
Expand Down Expand Up @@ -4193,12 +4195,19 @@ search_refined_method(rb_execution_context_t *ec, rb_control_frame_t *cfp, struc
static VALUE
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 }},
search_refined_method(ec, cfp, calling));
const rb_callable_method_entry_t *ref_cme = search_refined_method(ec, cfp, calling);

if (vm_cc_cme(ref_cc)) {
calling->cc= ref_cc;
return vm_call_method(ec, cfp, calling);
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;
return vm_call_method(ec, cfp, calling);
}
}
else {
return vm_call_method_nome(ec, cfp, calling);
Expand Down
17 changes: 8 additions & 9 deletions vm_method.c
Original file line number Diff line number Diff line change
Expand Up @@ -307,19 +307,19 @@ rb_clear_method_cache(VALUE klass_or_module, ID mid)
void rb_cc_table_free(VALUE klass);

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;
for (; v != (VALUE)vend; v += stride) {
void *ptr = asan_poisoned_object_p(v);
asan_unpoison_object(v, false);

if (RBASIC(v)->flags) { // liveness check
if (RB_TYPE_P(v, T_CLASS) ||
RB_TYPE_P(v, T_ICLASS)) {
if (RCLASS_CC_TBL(v)) {
rb_cc_table_free(v);
if (imemo_type_p(v, imemo_callcache)) {
const struct rb_callcache *cc = (const struct rb_callcache *)v;
if (vm_cc_refinement_p(cc) && cc->klass) {
vm_cc_invalidate(cc);
}
RCLASS_CC_TBL(v) = NULL;
}
}

Expand All @@ -331,10 +331,9 @@ invalidate_all_cc(void *vstart, void *vend, size_t stride, void *data)
}

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

Expand Down

0 comments on commit 48dccdb

Please sign in to comment.