[前][次][番号順一覧][スレッド一覧]

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/

[前][次][番号順一覧][スレッド一覧]