Project

General

Profile

Actions

Feature #14318

closed

Speedup `Proc#call` to mimic `yield`

Added by ko1 (Koichi Sasada) about 6 years ago. Updated about 6 years ago.

Status:
Closed
Target version:
[ruby-core:84650]

Description

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.

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:

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.

Actions

Also available in: Atom PDF

Like0
Like0