From: ko1@... Date: 2018-01-05T17:44:45+00:00 Subject: [ruby-core:84650] [Ruby trunk Feature#14318] Speedup `Proc#call` to mimic `yield` Issue #14318 has been reported by ko1 (Koichi Sasada). ---------------------------------------- Feature #14318: Speedup `Proc#call` to mimic `yield` https://bugs.ruby-lang.org/issues/14318 * Author: ko1 (Koichi Sasada) * Status: Open * Priority: Normal * Assignee: ko1 (Koichi Sasada) * Target version: 2.6 ---------------------------------------- We don't need to keep and restore`$SAFE` for `Proc#call`, we can replace the `Proc#call` process with `yield` process. The following patch acheves this replacement. ```diff Index: insns.def =================================================================== --- insns.def (revision 61623) +++ insns.def (working copy) @@ -947,11 +947,18 @@ invokeblock (VALUE val) // inc += 1 - ci->orig_argc; { struct rb_calling_info calling; + VALUE block_handler; + calling.argc = ci->orig_argc; calling.block_handler = VM_BLOCK_HANDLER_NONE; calling.recv = Qundef; /* should not be used */ - val = vm_invoke_block(ec, GET_CFP(), &calling, ci); + block_handler = VM_CF_BLOCK_HANDLER(GET_CFP()); + if (block_handler == VM_BLOCK_HANDLER_NONE) { + rb_vm_localjump_error("no block given (yield)", Qnil, 0); + } + + val = vm_invoke_block(ec, GET_CFP(), &calling, ci, block_handler); if (val == Qundef) { RESTORE_REGS(); NEXT_INSN(); Index: vm_insnhelper.c =================================================================== --- vm_insnhelper.c (revision 61623) +++ vm_insnhelper.c (working copy) @@ -2047,22 +2047,19 @@ vm_call_opt_send(rb_execution_context_t return vm_call_method(ec, reg_cfp, calling, ci, cc); } +static VALUE vm_invoke_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, VALUE block_handler); + static VALUE -vm_call_opt_call(rb_execution_context_t *ec, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc) +vm_call_opt_call(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc) { - rb_proc_t *proc; - int argc; - VALUE *argv; - - CALLER_SETUP_ARG(cfp, calling, ci); + VALUE procval = calling->recv; + int argc = calling->argc; - argc = calling->argc; - argv = ALLOCA_N(VALUE, argc); - GetProcPtr(calling->recv, proc); - MEMCPY(argv, cfp->sp - argc, VALUE, argc); - cfp->sp -= argc + 1; + /* remove self */ + if (argc > 0) MEMMOVE(&TOPN(argc), &TOPN(argc-1), VALUE, argc); + DEC_SP(1); - return rb_vm_invoke_proc(ec, proc, argc, argv, calling->block_handler); + return vm_invoke_block(ec, reg_cfp, calling, ci, VM_BH_FROM_PROC(procval)); } static VALUE @@ -2654,7 +2651,7 @@ vm_invoke_symbol_block(rb_execution_cont int argc; CALLER_SETUP_ARG(ec->cfp, calling, ci); argc = calling->argc; - val = vm_yield_with_symbol(ec, symbol, argc, STACK_ADDR_FROM_TOP(argc), VM_BLOCK_HANDLER_NONE); + val = vm_yield_with_symbol(ec, symbol, argc, STACK_ADDR_FROM_TOP(argc), calling->block_handler); POPN(argc); return val; } @@ -2668,7 +2665,7 @@ vm_invoke_ifunc_block(rb_execution_conte int argc; CALLER_SETUP_ARG(ec->cfp, calling, ci); argc = calling->argc; - val = vm_yield_with_cfunc(ec, captured, captured->self, argc, STACK_ADDR_FROM_TOP(argc), VM_BLOCK_HANDLER_NONE); + val = vm_yield_with_cfunc(ec, captured, captured->self, argc, STACK_ADDR_FROM_TOP(argc), calling->block_handler); POPN(argc); /* TODO: should put before C/yield? */ return val; } @@ -2693,17 +2690,10 @@ vm_proc_to_block_handler(VALUE procval) } static VALUE -vm_invoke_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_call_info *ci) +vm_invoke_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, VALUE block_handler) { - VALUE block_handler = VM_CF_BLOCK_HANDLER(reg_cfp); - VALUE type = GET_ISEQ()->body->local_iseq->body->type; int is_lambda = FALSE; - if ((type != ISEQ_TYPE_METHOD && type != ISEQ_TYPE_CLASS) || - block_handler == VM_BLOCK_HANDLER_NONE) { - rb_vm_localjump_error("no block given (yield)", Qnil, 0); - } - again: switch (vm_block_handler_type(block_handler)) { case block_handler_type_iseq: ``` The results are: ```ruby require 'benchmark' def block_yield yield(1, 2, 3) end def proc_yield yield(1, 2, 3) end def proc_call pr pr.call(1, 2, 3) end def proc_bp_yield &pr yield(1, 2, 3) end def proc_bp_call &pr pr.call(1, 2, 3) end N = 20_000_000 Benchmark.bm(15){|x| pr = Proc.new{} x.report("block_call"){ N.times{ block_yield{} } } x.report("proc_yield"){ N.times{ proc_yield &pr } } x.report("proc_call"){ N.times{ proc_call pr } } x.report("proc_bp_yield"){ N.times{ proc_bp_yield &pr } } x.report("proc_bp_call"){ N.times{ proc_bp_call &pr } } } __END__ current: user system total real block_call 1.094308 0.000000 1.094308 ( 1.092082) proc_yield 1.307741 0.000000 1.307741 ( 1.307547) proc_call 1.653297 0.000000 1.653297 ( 1.652242) proc_bp_yield 1.520334 0.000000 1.520334 ( 1.520187) proc_bp_call 2.056643 0.000000 2.056643 ( 2.056993) modified: user system total real block_call 1.142427 0.000000 1.142427 ( 1.141892) proc_yield 1.301262 0.000000 1.301262 ( 1.300968) proc_call 1.317821 0.003999 1.321820 ( 1.321477) # speed-up (about 25%) proc_bp_yield 1.644558 0.000001 1.644559 ( 1.644624) proc_bp_call 1.793974 0.004002 1.797976 ( 1.797777) # speed-up (about 15%) ``` Not so big improvement. -- https://bugs.ruby-lang.org/ Unsubscribe: