Last active
October 24, 2017 14:26
-
-
Save tarui/0638c56cc81b23a90be80fb9603be9ea to your computer and use it in GitHub Desktop.
feasibility study of Proc#call optimization
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'benchmark/ips' | |
f=proc{} | |
g=proc{|i| i>0 ? g.call(i-1):0 } | |
N=2000 | |
Benchmark.ips do |x| | |
x.report("f"){ f.call } | |
x.report("g"){ g.call(N) } | |
end | |
# ====== before ================================= | |
Warming up -------------------------------------- | |
f 266.208k i/100ms | |
g 235.000 i/100ms | |
Calculating ------------------------------------- | |
f 5.786M (± 2.3%) i/s - 29.017M in 5.017823s | |
g 2.306k (± 8.0%) i/s - 11.515k in 5.032498s | |
# ====== after ================================== | |
Warming up -------------------------------------- | |
f 301.080k i/100ms | |
g 799.000 i/100ms | |
Calculating ------------------------------------- | |
f 7.445M (± 1.7%) i/s - 37.334M in 5.015875s | |
g 7.975k (± 1.4%) i/s - 39.950k in 5.010102s | |
# =============================================== | |
# f: 7.445M / 5.786M = x 1.286 faster than before | |
# g: 7.975k / 2.306k = x 3.458 faster then before |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/vm.c b/vm.c | |
index 0ede2b8..9ea087f 100644 | |
--- a/vm.c | |
+++ b/vm.c | |
@@ -294,6 +294,8 @@ static VALUE vm_invoke_bmethod(rb_thread_t *th, rb_proc_t *proc, VALUE self, | |
int argc, const VALUE *argv, VALUE block_handler); | |
static VALUE vm_invoke_proc(rb_thread_t *th, rb_proc_t *proc, VALUE self, | |
int argc, const VALUE *argv, VALUE block_handler); | |
+static VALUE vm_invoke_proc2(rb_thread_t *th, rb_proc_t *proc, VALUE self, | |
+ int argc, const VALUE *argv, VALUE block_handler); | |
#include "vm_insnhelper.h" | |
#include "vm_exec.h" | |
@@ -981,6 +983,20 @@ invoke_block(rb_thread_t *th, const rb_iseq_t *iseq, VALUE self, const struct rb | |
iseq->body->stack_max); | |
return vm_exec(th); | |
} | |
+static inline VALUE | |
+setup_block(rb_thread_t *th, const rb_iseq_t *iseq, VALUE self, const struct rb_captured_block *captured, const rb_cref_t *cref, VALUE type, int opt_pc,int safe_level) | |
+{ | |
+ int arg_size = iseq->body->param.size; | |
+ | |
+ vm_push_frame(th, iseq, type | VM_FRAME_MAGIC_BLOCK , self, | |
+ VM_GUARDED_PREV_EP(captured->ep), | |
+ (VALUE)cref, /* cref or method */ | |
+ iseq->body->iseq_encoded + opt_pc, | |
+ th->ec.cfp->sp + arg_size, | |
+ iseq->body->local_table_size - arg_size, | |
+ iseq->body->stack_max)->safe_level_backup = safe_level; | |
+ return Qundef; | |
+} | |
static VALUE | |
invoke_bmethod(rb_thread_t *th, const rb_iseq_t *iseq, VALUE self, const struct rb_captured_block *captured, const rb_callable_method_entry_t *me, VALUE type, int opt_pc) | |
@@ -1037,6 +1053,38 @@ invoke_iseq_block_from_c(rb_thread_t *th, const struct rb_captured_block *captur | |
return invoke_bmethod(th, iseq, self, captured, me, type, opt_pc); | |
} | |
} | |
+static inline VALUE | |
+invoke_iseq_block_from_c2(rb_thread_t *th, const struct rb_captured_block *captured, | |
+ VALUE self, int argc, const VALUE *argv, VALUE passed_block_handler, | |
+ const rb_cref_t *cref, int is_lambda, int safe_level) | |
+{ | |
+ const rb_iseq_t *iseq = rb_iseq_check(captured->code.iseq); | |
+ int i, opt_pc; | |
+ VALUE type = VM_FRAME_MAGIC_BLOCK | (is_lambda ? VM_FRAME_FLAG_LAMBDA : 0); | |
+ rb_control_frame_t *cfp = th->ec.cfp; | |
+ VALUE *sp = cfp->sp; | |
+ const rb_callable_method_entry_t *me = th->passed_bmethod_me; | |
+ th->passed_bmethod_me = NULL; | |
+ stack_check(th); | |
+ | |
+ CHECK_VM_STACK_OVERFLOW(cfp, argc); | |
+ cfp->sp = sp + argc; | |
+ for (i=0; i<argc; i++) { | |
+ sp[i] = argv[i]; | |
+ } | |
+ | |
+ opt_pc = vm_yield_setup_args(th, iseq, argc, sp, passed_block_handler, | |
+ (is_lambda ? arg_setup_method : arg_setup_block)); | |
+ cfp->sp = sp; | |
+ | |
+ if (me == NULL) { | |
+ return setup_block(th, iseq, self, captured, cref, type, opt_pc,safe_level); | |
+ } | |
+ else { | |
+ abort(); | |
+ return invoke_bmethod(th, iseq, self, captured, me, type, opt_pc); | |
+ } | |
+} | |
static inline VALUE | |
invoke_block_from_c_bh(rb_thread_t *th, VALUE block_handler, | |
@@ -1162,12 +1210,63 @@ vm_invoke_proc(rb_thread_t *th, rb_proc_t *proc, VALUE self, | |
} | |
static VALUE | |
+vm_invoke_proc2(rb_thread_t *th, rb_proc_t *proc, VALUE self, | |
+ int argc, const VALUE *argv, VALUE passed_block_handler) | |
+{ | |
+ VALUE val = Qundef; | |
+ enum ruby_tag_type state; | |
+ volatile int stored_safe = th->ec.safe_level; | |
+ | |
+ if (vm_block_type(&proc->block)==block_type_iseq){ | |
+ th->ec.safe_level= proc->safe_level; | |
+ return invoke_iseq_block_from_c2(th, &proc->block.as.captured, self, argc, argv, passed_block_handler, NULL, proc->is_lambda,proc->safe_level); | |
+ } | |
+ | |
+ TH_PUSH_TAG(th); | |
+ if ((state = EXEC_TAG()) == TAG_NONE) { | |
+ th->ec.safe_level = proc->safe_level; | |
+ val = invoke_block_from_c_proc(th, proc, self, argc, argv, passed_block_handler, proc->is_lambda); | |
+ } | |
+ TH_POP_TAG(); | |
+ | |
+ th->ec.safe_level = stored_safe; | |
+ | |
+ if (state) { | |
+ TH_JUMP_TAG(th, state); | |
+ } | |
+ return val; | |
+} | |
+ | |
+static VALUE | |
vm_invoke_bmethod(rb_thread_t *th, rb_proc_t *proc, VALUE self, | |
int argc, const VALUE *argv, VALUE block_handler) | |
{ | |
return invoke_block_from_c_proc(th, proc, self, argc, argv, block_handler, TRUE); | |
} | |
+static VALUE | |
+vm_invoke_bmethod2(rb_thread_t *th, rb_proc_t *proc, VALUE self, | |
+ int argc, const VALUE *argv, VALUE block_handler) | |
+{ | |
+ if (vm_block_type(&proc->block)==block_type_iseq){ | |
+ return invoke_iseq_block_from_c2(th, &proc->block.as.captured, self, argc, argv, block_handler, NULL, TRUE,-1); | |
+ } | |
+ return invoke_block_from_c_proc(th, proc, self, argc, argv, block_handler, TRUE); | |
+} | |
+ | |
+VALUE | |
+rb_vm_invoke_proc2(rb_thread_t *th, rb_proc_t *proc, | |
+ int argc, const VALUE *argv, VALUE passed_block_handler) | |
+{ | |
+ VALUE self = vm_block_self(&proc->block); | |
+ vm_block_handler_verify(passed_block_handler); | |
+ if (proc->is_from_method) { | |
+ return vm_invoke_bmethod2(th, proc, self, argc, argv, passed_block_handler); | |
+ } | |
+ else { | |
+ return vm_invoke_proc2(th, proc, self, argc, argv, passed_block_handler); | |
+ } | |
+} | |
VALUE | |
rb_vm_invoke_proc(rb_thread_t *th, rb_proc_t *proc, | |
int argc, const VALUE *argv, VALUE passed_block_handler) | |
diff --git a/vm_core.h b/vm_core.h | |
index 13de0c5..2114be9 100644 | |
--- a/vm_core.h | |
+++ b/vm_core.h | |
@@ -666,10 +666,10 @@ typedef struct rb_control_frame_struct { | |
VALUE self; /* cfp[3] / block[0] */ | |
const VALUE *ep; /* cfp[4] / block[1] */ | |
const void *block_code; /* cfp[5] / block[2] */ /* iseq or ifunc */ | |
- | |
#if VM_DEBUG_BP_CHECK | |
VALUE *bp_check; /* cfp[6] */ | |
#endif | |
+ int safe_level_backup; | |
} rb_control_frame_t; | |
extern const rb_data_type_t ruby_threadptr_data_type; | |
@@ -1489,6 +1489,7 @@ void rb_iseq_pathobj_set(const rb_iseq_t *iseq, VALUE path, VALUE realpath); | |
int rb_thread_method_id_and_class(rb_thread_t *th, ID *idp, ID *called_idp, VALUE *klassp); | |
VALUE rb_vm_invoke_proc(rb_thread_t *th, rb_proc_t *proc, int argc, const VALUE *argv, VALUE block_handler); | |
+VALUE rb_vm_invoke_proc2(rb_thread_t *th, rb_proc_t *proc, int argc, const VALUE *argv, VALUE block_handler); | |
VALUE rb_vm_make_proc_lambda(rb_thread_t *th, const struct rb_captured_block *captured, VALUE klass, int8_t is_lambda); | |
VALUE rb_vm_make_proc(rb_thread_t *th, const struct rb_captured_block *captured, VALUE klass); | |
VALUE rb_vm_make_binding(rb_thread_t *th, const rb_control_frame_t *src_cfp); | |
diff --git a/vm_insnhelper.c b/vm_insnhelper.c | |
index 652b3d8..3c2b84a 100644 | |
--- a/vm_insnhelper.c | |
+++ b/vm_insnhelper.c | |
@@ -246,6 +246,7 @@ vm_push_frame_(rb_execution_context_t *ec, | |
#if VM_DEBUG_BP_CHECK | |
cfp->bp_check = sp + 1; | |
#endif | |
+ cfp->safe_level_backup=-1; | |
if (VMDEBUG == 2) { | |
SDR(); | |
@@ -293,6 +294,10 @@ vm_pop_frame(rb_thread_t *th, rb_control_frame_t *cfp, const VALUE *ep) | |
if (VM_CHECK_MODE >= 4) rb_gc_verify_internal_consistency(); | |
if (VMDEBUG == 2) SDR(); | |
+ if (UNLIKELY(cfp->safe_level_backup >= 0)){ | |
+ th->ec.safe_level=cfp->safe_level_backup; | |
+ } | |
+ | |
th->ec.cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); | |
return flags & VM_FRAME_FLAG_FINISH; | |
@@ -2049,6 +2054,9 @@ vm_call_opt_send(rb_thread_t *th, rb_control_frame_t *reg_cfp, struct rb_calling | |
} | |
static VALUE | |
+vm_exec(rb_thread_t *th); | |
+ | |
+static VALUE | |
vm_call_opt_call(rb_thread_t *th, rb_control_frame_t *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc) | |
{ | |
rb_proc_t *proc; | |
@@ -2063,7 +2071,7 @@ vm_call_opt_call(rb_thread_t *th, rb_control_frame_t *cfp, struct rb_calling_inf | |
MEMCPY(argv, cfp->sp - argc, VALUE, argc); | |
cfp->sp -= argc + 1; | |
- return rb_vm_invoke_proc(th, proc, argc, argv, calling->block_handler); | |
+ return rb_vm_invoke_proc2(th, proc, argc, argv, calling->block_handler); | |
} | |
static VALUE |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment