ruby-changes:40173
From: ko1 <ko1@a...>
Date: Sat, 24 Oct 2015 02:53:47 +0900 (JST)
Subject: [ruby-changes:40173] ko1:r52254 (trunk): * vm_insnhelper.c: introduce new call handler for simple ISeqs.
ko1 2015-10-24 02:53:35 +0900 (Sat, 24 Oct 2015) New Revision: 52254 http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=52254 Log: * vm_insnhelper.c: introduce new call handler for simple ISeqs. vm_call_iseq_setup_normal_0start() is simple, however it has some loops/conditions depends on ISeq::param.size and ISeq::local_size (in vm_push_frame(), inlined into this function). There are many simple methods which has a few parameters and local variables. So that this patch introduces several special functions generated in vm_call_iseq_optimized.inc by tool/mk_call_iseq_optimized.rb. This script makes vm_call_iseq_setup_normal_0start_Xparams_Ylocals() where X is 0 to 3 and Y is 1 to 6 (as current setting). In this case, X * Y = 24 functions are created. These functions creates fast method dispatch by inlining vm_push_frame() with immediate params/locals sizes. On my laptop, we can have the following results. vm2_method* 1.083 (8.3% faster) vm2_poly_method* 0.961 (3.4% slower) It shows 8.3% faster for inner loop method dispatch (hit inline cache), but 3.4% slower when inline cache miss because we need to find a suitable call handler. * common.mk: add a rule for vm_call_iseq_optimized.inc. * tool/mk_call_iseq_optimized.rb: added. * vm.c: include vm_call_iseq_optimized.inc. Added files: trunk/tool/mk_call_iseq_optimized.rb Modified files: trunk/ChangeLog trunk/common.mk trunk/vm.c trunk/vm_insnhelper.c Index: ChangeLog =================================================================== --- ChangeLog (revision 52253) +++ ChangeLog (revision 52254) @@ -1,3 +1,39 @@ https://github.com/ruby/ruby/blob/trunk/ChangeLog#L1 +Sat Oct 24 02:02:24 2015 Koichi Sasada <ko1@a...> + + * vm_insnhelper.c: introduce new call handler for simple ISeqs. + + vm_call_iseq_setup_normal_0start() is simple, however it has + some loops/conditions depends on ISeq::param.size and + ISeq::local_size (in vm_push_frame(), inlined into this function). + + There are many simple methods which has a few parameters and local + variables. So that this patch introduces several special functions + generated in vm_call_iseq_optimized.inc by + tool/mk_call_iseq_optimized.rb. + + This script makes + vm_call_iseq_setup_normal_0start_Xparams_Ylocals() + where X is 0 to 3 and Y is 1 to 6 (as current setting). + In this case, X * Y = 24 functions are created. + + These functions creates fast method dispatch by inlining + vm_push_frame() with immediate params/locals sizes. + + On my laptop, we can have the following results. + + vm2_method* 1.083 (8.3% faster) + vm2_poly_method* 0.961 (3.4% slower) + + It shows 8.3% faster for inner loop method dispatch (hit inline + cache), but 3.4% slower when inline cache miss because we need + to find a suitable call handler. + + * common.mk: add a rule for vm_call_iseq_optimized.inc. + + * tool/mk_call_iseq_optimized.rb: added. + + * vm.c: include vm_call_iseq_optimized.inc. + Sat Oct 24 01:58:50 2015 Koichi Sasada <ko1@a...> * vm_core.h: define vm_call_handler. Index: common.mk =================================================================== --- common.mk (revision 52253) +++ common.mk (revision 52254) @@ -819,6 +819,10 @@ known_errors.inc: $(srcdir)/template/kno https://github.com/ruby/ruby/blob/trunk/common.mk#L819 $(ECHO) generating $@ $(Q) $(BASERUBY) $(srcdir)/tool/generic_erb.rb -c -o $@ $(srcdir)/template/known_errors.inc.tmpl $(srcdir)/defs/known_errors.def +vm_call_iseq_optimized.inc: $(srcdir)/tool/mk_call_iseq_optimized.rb + $(ECHO) generating $@ + $(Q) $(BASERUBY) $(srcdir)/tool/mk_call_iseq_optimized.rb > vm_call_iseq_optimized.inc + $(MINIPRELUDE_C): $(COMPILE_PRELUDE) $(ECHO) generating $@ $(Q) $(BASERUBY) $(srcdir)/tool/generic_erb.rb -I$(srcdir) -o $@ \ @@ -2391,6 +2395,7 @@ vm.$(OBJEXT): {$(VPATH)}thread_native.h https://github.com/ruby/ruby/blob/trunk/common.mk#L2395 vm.$(OBJEXT): {$(VPATH)}vm.c vm.$(OBJEXT): {$(VPATH)}vm.h vm.$(OBJEXT): {$(VPATH)}vm.inc +vm.$(OBJEXT): {$(VPATH)}vm_call_iseq_optimized.inc vm.$(OBJEXT): {$(VPATH)}vm_args.c vm.$(OBJEXT): {$(VPATH)}vm_core.h vm.$(OBJEXT): {$(VPATH)}vm_debug.h @@ -2431,6 +2436,9 @@ vm_backtrace.$(OBJEXT): {$(VPATH)}vm_bac https://github.com/ruby/ruby/blob/trunk/common.mk#L2436 vm_backtrace.$(OBJEXT): {$(VPATH)}vm_core.h vm_backtrace.$(OBJEXT): {$(VPATH)}vm_debug.h vm_backtrace.$(OBJEXT): {$(VPATH)}vm_opts.h +vm_call.$(OBJEXT): $(top_srcdir)/include/ruby.h +vm_call.$(OBJEXT): {$(VPATH)}vm_core.h +vm_call.$(OBJEXT): {$(VPATH)}vm_call_iseq_optimized.inc vm_dump.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h vm_dump.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h vm_dump.$(OBJEXT): $(CCAN_DIR)/list/list.h Index: vm.c =================================================================== --- vm.c (revision 52253) +++ vm.c (revision 52254) @@ -3111,3 +3111,5 @@ vm_collect_usage_register(int reg, int i https://github.com/ruby/ruby/blob/trunk/vm.c#L3111 (*ruby_vm_collect_usage_func_register)(reg, isset); } #endif + +#include "vm_call_iseq_optimized.inc" /* required from vm_insnhelper.c */ Index: vm_insnhelper.c =================================================================== --- vm_insnhelper.c (revision 52253) +++ vm_insnhelper.c (revision 52254) @@ -1235,17 +1235,17 @@ vm_base_ptr(rb_control_frame_t *cfp) https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L1235 #include "vm_args.c" -static inline VALUE vm_call_iseq_setup_2(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, int opt_pc); -static inline VALUE vm_call_iseq_setup_normal(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, int opt_pc); +static inline VALUE vm_call_iseq_setup_2(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, int opt_pc, int param_size, int local_size); +static inline VALUE vm_call_iseq_setup_normal(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, int opt_pc, int param_size, int local_size); static inline VALUE vm_call_iseq_setup_tailcall(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, int opt_pc); -static VALUE vm_call_iseq_setup_normal_0start(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); -static VALUE vm_call_iseq_setup_tailcall_0start(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); - static VALUE vm_call_super_method(rb_thread_t *th, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc); static VALUE vm_call_method_nome(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); static VALUE vm_call_method_each_type(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); static inline VALUE vm_call_method(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); +typedef VALUE (*vm_call_handler)(struct rb_thread_struct *th, struct rb_control_frame_struct *cfp, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc); +static vm_call_handler vm_call_iseq_setup_func(const struct rb_call_info *ci, const int param_size, const int local_size); + static rb_method_definition_t *method_definition_create(rb_method_type_t type, ID mid); static void method_definition_set(const rb_method_entry_t *me, rb_method_definition_t *def, void *opts); static int rb_method_definition_eq(const rb_method_definition_t *d1, const rb_method_definition_t *d2); @@ -1332,8 +1332,33 @@ vm_callee_setup_block_arg(rb_thread_t *t https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L1332 } } +static const rb_iseq_t * +def_iseq_ptr(rb_method_definition_t *def) +{ +#if VM_CHECK_MODE > 0 + if (def->type != VM_METHOD_TYPE_ISEQ) rb_bug("def_iseq_ptr: not iseq (%d)", def->type); +#endif + return def->body.iseq.iseqptr; +} + +static VALUE +vm_call_iseq_setup_tailcall_0start(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) +{ + return vm_call_iseq_setup_tailcall(th, cfp, calling, ci, cc, 0); +} + +static VALUE +vm_call_iseq_setup_normal_0start(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) +{ + const rb_iseq_t *iseq = def_iseq_ptr(cc->me->def); + int param = iseq->body->param.size; + int local = iseq->body->local_size; + return vm_call_iseq_setup_normal(th, cfp, calling, ci, cc, 0, param, local); +} + static inline int -vm_callee_setup_arg(rb_thread_t *th, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc, const rb_iseq_t *iseq, VALUE *argv) +vm_callee_setup_arg(rb_thread_t *th, struct rb_calling_info *calling, const struct rb_call_info *ci, struct rb_call_cache *cc, + const rb_iseq_t *iseq, VALUE *argv, int param_size, int local_size) { if (LIKELY(simple_iseq_p(iseq))) { rb_control_frame_t *cfp = th->cfp; @@ -1344,9 +1369,7 @@ vm_callee_setup_arg(rb_thread_t *th, str https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L1369 argument_arity_error(th, iseq, calling->argc, iseq->body->param.lead_num, iseq->body->param.lead_num); } - CI_SET_FASTPATH(cc, - (UNLIKELY(ci->flag & VM_CALL_TAILCALL) ? vm_call_iseq_setup_tailcall_0start : - vm_call_iseq_setup_normal_0start), + CI_SET_FASTPATH(cc, vm_call_iseq_setup_func(ci, param_size, local_size), (!IS_ARGS_SPLAT(ci) && !IS_ARGS_KEYWORD(ci) && !(METHOD_ENTRY_VISI(cc->me) == METHOD_VISI_PROTECTED))); return 0; @@ -1356,27 +1379,22 @@ vm_callee_setup_arg(rb_thread_t *th, str https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L1379 } } -static const rb_iseq_t * -def_iseq_ptr(rb_method_definition_t *def) -{ -#if VM_CHECK_MODE > 0 - if (def->type != VM_METHOD_TYPE_ISEQ) rb_bug("def_iseq_ptr: not iseq (%d)", def->type); -#endif - return def->body.iseq.iseqptr; -} - static VALUE vm_call_iseq_setup(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) { - int opt_pc = vm_callee_setup_arg(th, calling, ci, cc, def_iseq_ptr(cc->me->def), cfp->sp - calling->argc); - return vm_call_iseq_setup_2(th, cfp, calling, ci, cc, opt_pc); + const rb_iseq_t *iseq = def_iseq_ptr(cc->me->def); + const int param_size = iseq->body->param.size; + const int local_size = iseq->body->local_size; + const int opt_pc = vm_callee_setup_arg(th, calling, ci, cc, def_iseq_ptr(cc->me->def), cfp->sp - calling->argc, param_size, local_size); + return vm_call_iseq_setup_2(th, cfp, calling, ci, cc, opt_pc, param_size, local_size); } static inline VALUE -vm_call_iseq_setup_2(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, int opt_pc) +vm_call_iseq_setup_2(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, + int opt_pc, int param_size, int local_size) { if (LIKELY(!(ci->flag & VM_CALL_TAILCALL))) { - return vm_call_iseq_setup_normal(th, cfp, calling, ci, cc, opt_pc); + return vm_call_iseq_setup_normal(th, cfp, calling, ci, cc, opt_pc, param_size, local_size); } else { return vm_call_iseq_setup_tailcall(th, cfp, calling, ci, cc, opt_pc); @@ -1384,24 +1402,26 @@ vm_call_iseq_setup_2(rb_thread_t *th, rb https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L1402 } static inline VALUE -vm_call_iseq_setup_normal(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, int opt_pc) +vm_call_iseq_setup_normal(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, + int opt_pc, int param_size, int local_size) { const rb_callable_method_entry_t *me = cc->me; const rb_iseq_t *iseq = def_iseq_ptr(me->def); VALUE *argv = cfp->sp - calling->argc; - VALUE *sp = argv + iseq->body->param.size; + VALUE *sp = argv + param_size; cfp->sp = argv - 1 /* recv */; vm_push_frame(th, iseq, VM_FRAME_MAGIC_METHOD, calling->recv, VM_ENVVAL_BLOCK_PTR(calling->blockptr), (VALUE)me, iseq->body->iseq_encoded + opt_pc, sp, - iseq->body->local_size - iseq->body->param.size, + local_size - param_size, iseq->body->stack_max); return Qundef; } static inline VALUE -vm_call_iseq_setup_tailcall(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, int opt_pc) +vm_call_iseq_setup_tailcall(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, + int opt_pc) { unsigned int i; VALUE *argv = cfp->sp - calling->argc; @@ -1437,18 +1457,6 @@ vm_call_iseq_setup_tailcall(rb_thread_t https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L1457 } static VALUE -vm_call_iseq_setup_normal_0start(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) -{ - return vm_call_iseq_setup_normal(th, cfp, calling, ci, cc, 0); -} - -static VALUE -vm_call_iseq_setup_tailcall_0start(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) -{ - return vm_call_iseq_setup_tailcall(th, cfp, calling, ci, cc, 0); -} - -static VALUE call_cfunc_m2(VALUE (*func)(ANYARGS), VALUE recv, int argc, const VALUE *argv) { return (*func)(recv, rb_ary_new4(argc, argv)); Index: tool/mk_call_iseq_optimized.rb =================================================================== --- tool/mk_call_iseq_optimized.rb (revision 0) +++ tool/mk_call_iseq_optimized.rb (revision 52254) @@ -0,0 +1,72 @@ https://github.com/ruby/ruby/blob/trunk/tool/mk_call_iseq_optimized.rb#L1 + +puts <<EOS +#if 1 /* enable or disable this optimization */ + +/* DO NOT EDIT THIS FILE DIRECTLY + * + * This file is enerated by tool/mkcall_iseq.rb + */ + +EOS + +P = (0..3) +L = (1..6) + +def fname param, local + "vm_call_iseq_setup_normal_0start_#{param}params_#{local}locals" +end + +P.each{|param| + L.each{|local| + puts <<EOS +static VALUE +#{fname(param, local)}(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) +{ + return vm_call_iseq_setup_normal(th, cfp, calling, ci, cc, 0, #{param}, #{local}); +} + +EOS + # + } +} + +puts <<EOS +/* vm_call_iseq_handlers[param][local] */ +static const vm_call_handler vm_call_iseq_handlers[][#{L.to_a.size}] = { +#{P.map{|param| '{' + L.map{|local| fname(param, local)}.join(",\n ") + '}'}.join(",\n")} +}; + +static inline vm_call_handler +vm_call_iseq_setup_func(const struct rb_call_info *ci, const int param_size, const int local_size) +{ + if (UNLIKELY(ci->flag & VM_CALL_TAILCALL)) { + return vm_call_iseq_setup_tailcall_0start; + } + else if (0) { /* to disable optimize */ + return vm_call_iseq_setup_normal_0start; + } + else { + if (param_size <= #{P.end} && + local_size <= #{L.end}) { + VM_ASSERT(local_size != 0); + return vm_call_iseq_handlers[param_size][local_size-1]; + } + return vm_call_iseq_setup_normal_0start; + } +} + +#else + + +static inline vm_call_handler +vm_call_iseq_setup_func(const struct rb_call_info *ci, struct rb_call_cache *cc) +{ + if (UNLIKELY(ci->flag & VM_CALL_TAILCALL)) { + return vm_call_iseq_setup_tailcall_0start; + } + else { + return vm_call_iseq_setup_normal_0start; + } +} +#endif +EOS -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/