mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
proc.c: make bind_call use existing callable method entry when possible
The most common use case for `bind_call` is to protect from core
methods being redefined, for instance a typical use:
```ruby
UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name)
def real_mod_name(mod)
UNBOUND_METHOD_MODULE_NAME.bind_call(mod)
end
```
But it's extremely common that the method wasn't actually redefined.
In such case we can avoid creating a new callable method entry,
and simply delegate to the receiver.
This result in a 1.5-2X speed-up for the fast path, and little to
no impact on the slowpath:
```
compare-ruby: ruby 3.1.0dev (2021-02-05T06:33:00Z master b2674c1fd7
) [x86_64-darwin19]
built-ruby: ruby 3.1.0dev (2021-02-15T10:35:17Z bind-call-fastpath d687e06615) [x86_64-darwin19]
| |compare-ruby|built-ruby|
|:---------|-----------:|---------:|
|fastpath | 11.325M| 16.393M|
| | -| 1.45x|
|slowpath | 10.488M| 10.242M|
| | 1.02x| -|
```
This commit is contained in:
parent
d9fea496af
commit
a03653d386
Notes:
git
2021-03-11 06:43:43 +09:00
2 changed files with 37 additions and 13 deletions
16
benchmark/method_bind_call.yml
Normal file
16
benchmark/method_bind_call.yml
Normal file
|
@ -0,0 +1,16 @@
|
|||
prelude: |
|
||||
named_module = Kernel
|
||||
|
||||
module FakeName
|
||||
def self.name
|
||||
"NotMyame".freeze
|
||||
end
|
||||
end
|
||||
|
||||
MOD_NAME = Module.instance_method(:name)
|
||||
|
||||
benchmark:
|
||||
fastpath: MOD_NAME.bind_call(Kernel)
|
||||
slowpath: MOD_NAME.bind_call(FakeName)
|
||||
|
||||
loop_count: 100_000
|
34
proc.c
34
proc.c
|
@ -18,6 +18,7 @@
|
|||
#include "internal/object.h"
|
||||
#include "internal/proc.h"
|
||||
#include "internal/symbol.h"
|
||||
#include "method.h"
|
||||
#include "iseq.h"
|
||||
#include "vm_core.h"
|
||||
|
||||
|
@ -2512,12 +2513,8 @@ rb_method_call_with_block(int argc, const VALUE *argv, VALUE method, VALUE passe
|
|||
*/
|
||||
|
||||
static void
|
||||
convert_umethod_to_method_components(VALUE method, VALUE recv, VALUE *methclass_out, VALUE *klass_out, VALUE *iclass_out, const rb_method_entry_t **me_out)
|
||||
convert_umethod_to_method_components(const struct METHOD *data, VALUE recv, VALUE *methclass_out, VALUE *klass_out, VALUE *iclass_out, const rb_method_entry_t **me_out)
|
||||
{
|
||||
struct METHOD *data;
|
||||
|
||||
TypedData_Get_Struct(method, struct METHOD, &method_data_type, data);
|
||||
|
||||
VALUE methclass = data->me->owner;
|
||||
VALUE iclass = data->me->defined_class;
|
||||
VALUE klass = CLASS_OF(recv);
|
||||
|
@ -2598,7 +2595,9 @@ umethod_bind(VALUE method, VALUE recv)
|
|||
{
|
||||
VALUE methclass, klass, iclass;
|
||||
const rb_method_entry_t *me;
|
||||
convert_umethod_to_method_components(method, recv, &methclass, &klass, &iclass, &me);
|
||||
const struct METHOD *data;
|
||||
TypedData_Get_Struct(method, struct METHOD, &method_data_type, data);
|
||||
convert_umethod_to_method_components(data, recv, &methclass, &klass, &iclass, &me);
|
||||
|
||||
struct METHOD *bound;
|
||||
method = TypedData_Make_Struct(rb_cMethod, struct METHOD, &method_data_type, bound);
|
||||
|
@ -2626,15 +2625,24 @@ umethod_bind_call(int argc, VALUE *argv, VALUE method)
|
|||
argc--;
|
||||
argv++;
|
||||
|
||||
VALUE methclass, klass, iclass;
|
||||
const rb_method_entry_t *me;
|
||||
convert_umethod_to_method_components(method, recv, &methclass, &klass, &iclass, &me);
|
||||
struct METHOD bound = { recv, klass, 0, me };
|
||||
|
||||
VALUE passed_procval = rb_block_given_p() ? rb_block_proc() : Qnil;
|
||||
|
||||
rb_execution_context_t *ec = GET_EC();
|
||||
return call_method_data(ec, &bound, argc, argv, passed_procval, RB_PASS_CALLED_KEYWORDS);
|
||||
|
||||
const struct METHOD *data;
|
||||
TypedData_Get_Struct(method, struct METHOD, &method_data_type, data);
|
||||
|
||||
const rb_callable_method_entry_t *cme = rb_callable_method_entry(CLASS_OF(recv), data->me->called_id);
|
||||
if (data->me == (const rb_method_entry_t *)cme) {
|
||||
vm_passed_block_handler_set(ec, proc_to_block_handler(passed_procval));
|
||||
return rb_vm_call_kw(ec, recv, cme->called_id, argc, argv, cme, RB_PASS_CALLED_KEYWORDS);
|
||||
} else {
|
||||
VALUE methclass, klass, iclass;
|
||||
const rb_method_entry_t *me;
|
||||
convert_umethod_to_method_components(data, recv, &methclass, &klass, &iclass, &me);
|
||||
struct METHOD bound = { recv, klass, 0, me };
|
||||
|
||||
return call_method_data(ec, &bound, argc, argv, passed_procval, RB_PASS_CALLED_KEYWORDS);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
Loading…
Reference in a new issue