Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tarui/0638c56cc81b23a90be80fb9603be9ea to your computer and use it in GitHub Desktop.
Save tarui/0638c56cc81b23a90be80fb9603be9ea to your computer and use it in GitHub Desktop.
feasibility study of Proc#call optimization
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
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