ruby-changes:65438
From: Jean <ko1@a...>
Date: Thu, 11 Mar 2021 06:43:42 +0900 (JST)
Subject: [ruby-changes:65438] a03653d386 (master): proc.c: make bind_call use existing callable method entry when possible
https://git.ruby-lang.org/ruby.git/commit/?id=a03653d386 From a03653d386bd64256932ea7eead3c28f03de1bac Mon Sep 17 00:00:00 2001 From: Jean Boussier <jean.boussier@g...> Date: Fri, 12 Feb 2021 17:31:19 +0100 Subject: 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| -| ``` --- benchmark/method_bind_call.yml | 16 ++++++++++++++++ proc.c | 34 +++++++++++++++++++++------------- 2 files changed, 37 insertions(+), 13 deletions(-) create mode 100644 benchmark/method_bind_call.yml diff --git a/benchmark/method_bind_call.yml b/benchmark/method_bind_call.yml new file mode 100644 index 0000000..9e0e046 --- /dev/null +++ b/benchmark/method_bind_call.yml @@ -0,0 +1,16 @@ https://github.com/ruby/ruby/blob/trunk/benchmark/method_bind_call.yml#L1 +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 diff --git a/proc.c b/proc.c index d1e8358..b266365 100644 --- a/proc.c +++ b/proc.c @@ -18,6 +18,7 @@ https://github.com/ruby/ruby/blob/trunk/proc.c#L18 #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 https://github.com/ruby/ruby/blob/trunk/proc.c#L2513 */ 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) https://github.com/ruby/ruby/blob/trunk/proc.c#L2595 { 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) https://github.com/ruby/ruby/blob/trunk/proc.c#L2625 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); + } } /* -- cgit v1.1 -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/