optimize method dispatch for lead/opt params.

There is a special optimization for "only lead parameters"
method dispatch using specialized dispatcher functions
`vm_call_iseq_setup_normal_0start...`.
Other cases (opt, rest, post, ...) we don't use specialized
dispatcher and call with `setup_parameters_complex` to
satisfy Ruby's complex parameter specification.

This commit introduce a specialize dispatcher for
methods which use only lead and optional parameters.

Two step improvements:
(1) prepare "lead/opt" only check pass.
    It is to skip the `setup_parameters_complex` function.
(2) introduce specialized dispatcher for only "lead/opt"
    parameters methods (vm_call_iseq_setup_normal_opt_start).

With these improvements, we achieved good micro-benchmark
results:
  With a method: `def opt2 a, b=nil; end`
  With the following binaries:
    clean-miniruby: unmodified trunk.
    opt_miniruby: apply step (1).
    opt_cc_miniruby: apply step (2).
  Result with benchmark-driver:

                        opt2(1)
   opt_cc_miniruby:  42269409.1 i/s
      opt_miniruby:  36304428.3 i/s - 1.16x  slower
    clean-miniruby:  25897409.5 i/s - 1.63x  slower

                     opt2(1, 2)
   opt_cc_miniruby:  45935145.7 i/s
      opt_miniruby:  40513196.9 i/s - 1.13x  slower
    clean-miniruby:  29976057.6 i/s - 1.53x  slower

This improvement may be trivial (difficult to improve practical
cases). However, this is enough small patch so I decide to
introduce it.


git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@67315 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
ko1 2019-03-20 19:57:39 +00:00
Родитель 139634a16f
Коммит 24e03d7e26
1 изменённых файлов: 90 добавлений и 13 удалений

Просмотреть файл

@ -1703,6 +1703,17 @@ rb_simple_iseq_p(const rb_iseq_t *iseq)
iseq->body->param.flags.has_block == FALSE;
}
bool
rb_iseq_only_optparam_p(const rb_iseq_t *iseq)
{
return iseq->body->param.flags.has_opt == TRUE &&
iseq->body->param.flags.has_rest == FALSE &&
iseq->body->param.flags.has_post == FALSE &&
iseq->body->param.flags.has_kw == FALSE &&
iseq->body->param.flags.has_kwrest == FALSE &&
iseq->body->param.flags.has_block == FALSE;
}
static inline void
CALLER_SETUP_ARG(struct rb_control_frame_struct *restrict cfp,
struct rb_calling_info *restrict calling,
@ -1716,27 +1727,93 @@ CALLER_SETUP_ARG(struct rb_control_frame_struct *restrict cfp,
}
}
#define USE_OPT_HIST 0
#if USE_OPT_HIST
#define OPT_HIST_MAX 64
static int opt_hist[OPT_HIST_MAX+1];
__attribute__((destructor))
static void
opt_hist_show_results_at_exit(void)
{
for (int i=0; i<OPT_HIST_MAX; i++) {
fprintf(stderr, "opt_hist\t%d\t%d\n", i, opt_hist[i]);
}
}
#endif
static VALUE
vm_call_iseq_setup_normal_opt_start(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)
{
const rb_iseq_t *iseq = def_iseq_ptr(cc->me->def);
const int lead_num = iseq->body->param.lead_num;
const int opt = calling->argc - lead_num;
const int opt_num = iseq->body->param.opt_num;
const int opt_pc = iseq->body->param.opt_table[opt];
const int param = iseq->body->param.size;
const int local = iseq->body->local_table_size;
const int delta = opt_num - opt;
#if USE_OPT_HIST
if (opt_pc < OPT_HIST_MAX) {
opt_hist[opt]++;
}
else {
opt_hist[OPT_HIST_MAX]++;
}
#endif
return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, opt_pc, param - delta, local);
}
static inline int
vm_callee_setup_arg(rb_execution_context_t *ec, 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(rb_simple_iseq_p(iseq) && !(ci->flag & VM_CALL_KW_SPLAT))) {
rb_control_frame_t *cfp = ec->cfp;
if (LIKELY(!(ci->flag & VM_CALL_KW_SPLAT))) {
if (LIKELY(rb_simple_iseq_p(iseq))) {
rb_control_frame_t *cfp = ec->cfp;
CALLER_SETUP_ARG(cfp, calling, ci); /* splat arg */
CALLER_SETUP_ARG(cfp, calling, ci); /* splat arg */
if (calling->argc != iseq->body->param.lead_num) {
argument_arity_error(ec, iseq, calling->argc, iseq->body->param.lead_num, iseq->body->param.lead_num);
}
if (calling->argc != iseq->body->param.lead_num) {
argument_arity_error(ec, iseq, calling->argc, iseq->body->param.lead_num, iseq->body->param.lead_num);
}
CC_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;
}
else if (rb_iseq_only_optparam_p(iseq) && !(ci->flag & VM_CALL_KW_SPLAT)) {
rb_control_frame_t *cfp = ec->cfp;
CALLER_SETUP_ARG(cfp, calling, ci); /* splat arg */
CC_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;
}
else {
return setup_parameters_complex(ec, iseq, calling, ci, argv, arg_setup_method);
const int lead_num = iseq->body->param.lead_num;
const int opt_num = iseq->body->param.opt_num;
const int argc = calling->argc;
const int opt = argc - lead_num;
if (opt < 0 || opt > opt_num) {
argument_arity_error(ec, iseq, argc, lead_num, lead_num + opt_num);
}
CC_SET_FASTPATH(cc, vm_call_iseq_setup_normal_opt_start,
!IS_ARGS_SPLAT(ci) && !IS_ARGS_KEYWORD(ci) &&
!(METHOD_ENTRY_VISI(cc->me) == METHOD_VISI_PROTECTED));
/* initialize opt vars for self-references */
VM_ASSERT(iseq->body->param.size == lead_num + opt_num);
for (int i=argc; i<lead_num + opt_num; i++) {
argv[i] = Qnil;
}
return (int)iseq->body->param.opt_table[opt];
}
}
return setup_parameters_complex(ec, iseq, calling, ci, argv, arg_setup_method);
}
static VALUE