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…
	
	Add table
		Add a link
		
	
		Reference in a new issue