[前][次][番号順一覧][スレッド一覧]

ruby-changes:55108

From: ko1 <ko1@a...>
Date: Thu, 21 Mar 2019 04:57:45 +0900 (JST)
Subject: [ruby-changes:55108] ko1:r67315 (trunk): optimize method dispatch for lead/opt params.

ko1	2019-03-21 04:57:39 +0900 (Thu, 21 Mar 2019)

  New Revision: 67315

  https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=67315

  Log:
    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.

  Modified files:
    trunk/vm_insnhelper.c
Index: vm_insnhelper.c
===================================================================
--- vm_insnhelper.c	(revision 67314)
+++ vm_insnhelper.c	(revision 67315)
@@ -1703,6 +1703,17 @@ rb_simple_iseq_p(const rb_iseq_t *iseq) https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L1703
 	   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 https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L1727
     }
 }
 
-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;
+#define USE_OPT_HIST 0
 
-	CALLER_SETUP_ARG(cfp, calling, ci); /* splat arg */
+#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
 
-	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;
+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 {
-	return setup_parameters_complex(ec, iseq, calling, ci, argv, arg_setup_method);
+        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(!(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 */
+
+            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 */
+
+            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

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]