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

ruby-changes:55127

From: ko1 <ko1@a...>
Date: Fri, 22 Mar 2019 09:21:48 +0900 (JST)
Subject: [ruby-changes:55127] ko1:r67333 (trunk): optimize method dispatch for lead/kw params.

ko1	2019-03-22 09:21:41 +0900 (Fri, 22 Mar 2019)

  New Revision: 67333

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

  Log:
    optimize method dispatch for lead/kw params.
    
    similar idea to r67315, provide the following optimization
    for method dispatch with lead and kw parameters.
    
    (1) add a special branch to check passing kw arguments to
        a method which has lead and kw parameters.
        ex) def foo(x, k:1); end; foo(0, k:1)
    (2) add a special branch to check passing no-kw arguments to
        a method which has lead and kw parameters.
        ex) def foo(x, k:1); end; foo(0)
    
    For (1) and (2) cases, provide special dispatchers. For (2) case,
    this patch only use the special dispatcher if all default
    kw parameters are literal values (nil, 1, and so on. In other case,
    kw->default_values does not contains Qundef) (and no required kw
    parameters becaseu they don't pass any keyword parameters).
    
    Passing keyword arguments with a hash object is not a scope of
    this patch.
    
    Without this patch, (1) and (2) cases use `setup_parameters_complex()`.
    Especially, (2) seems frequent case for methods which extend a normal
    usecase with keyword parameters (like: `exception: true`).
    
    We can measure the performance with benchmark-driver:
      With methods: def kw k1:1, k2:2; end
                    def m; end
      With the following binaries:
        clean-miniruby: unmodified trunk.
        opt_miniruby1: use special branches for lead/kw parameters.
        opt_miniruby2: use special dispatchers for lead/kw parameters.
        opt_cc_miniruby: apply step (2).
      Result with benchmark-driver:
    
                                  m
         opt_miniruby2:  75222278.0 i/s
        clean-miniruby:  73177896.5 i/s - 1.03x  slower
         opt_miniruby1:  62466783.3 i/s - 1.20x  slower
    
                                 kw
         opt_miniruby2:  52044504.4 i/s
         opt_miniruby1:  29142025.7 i/s - 1.79x  slower
        clean-miniruby:  20515235.4 i/s - 2.54x  slower
    
                          kw k1: 10
         opt_miniruby2:  26492219.5 i/s
         opt_miniruby1:  25409484.9 i/s - 1.04x  slower
        clean-miniruby:  20235113.7 i/s - 1.31x  slower
    
                  kw k1: 10, k2: 20
         opt_miniruby1:  24159534.0 i/s
         opt_miniruby2:  23470527.5 i/s - 1.03x  slower
        clean-miniruby:  17822621.5 i/s - 1.36x  slower

  Modified files:
    trunk/vm_insnhelper.c
Index: vm_insnhelper.c
===================================================================
--- vm_insnhelper.c	(revision 67332)
+++ vm_insnhelper.c	(revision 67333)
@@ -1714,6 +1714,18 @@ rb_iseq_only_optparam_p(const rb_iseq_t https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L1714
            iseq->body->param.flags.has_block == FALSE;
 }
 
+static bool
+rb_iseq_only_kwparam_p(const rb_iseq_t *iseq)
+{
+    return iseq->body->param.flags.has_opt == FALSE &&
+           iseq->body->param.flags.has_rest == FALSE &&
+           iseq->body->param.flags.has_post == FALSE &&
+           iseq->body->param.flags.has_kw == TRUE &&
+           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,
@@ -1769,6 +1781,57 @@ vm_call_iseq_setup_normal_opt_start(rb_e https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L1781
     return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, opt_pc, param - delta, local);
 }
 
+static void
+args_setup_kw_parameters(rb_execution_context_t *const ec, const rb_iseq_t *const iseq,
+                         VALUE *const passed_values, const int passed_keyword_len, const VALUE *const passed_keywords,
+			 VALUE *const locals);
+
+static VALUE
+vm_call_iseq_setup_kwparm_kwarg(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)
+{
+    VM_ASSERT(ci->flag & VM_CALL_KWARG);
+    const rb_iseq_t *iseq = def_iseq_ptr(cc->me->def);
+    const struct rb_iseq_param_keyword *kw_param = iseq->body->param.keyword;
+    const struct rb_call_info_kw_arg *kw_arg = ((struct rb_call_info_with_kwarg *)ci)->kw_arg;
+    const int ci_kw_len = kw_arg->keyword_len;
+    const VALUE * const ci_keywords = kw_arg->keywords;
+    VALUE *argv = cfp->sp - calling->argc;
+    VALUE *const klocals = argv + kw_param->bits_start - kw_param->num;
+    const int lead_num = iseq->body->param.lead_num;
+    VALUE * const ci_kws = ALLOCA_N(VALUE, ci_kw_len);
+    MEMCPY(ci_kws, argv + lead_num, VALUE, ci_kw_len);
+    args_setup_kw_parameters(ec, iseq, ci_kws, ci_kw_len, ci_keywords, klocals);
+
+    int param = iseq->body->param.size;
+    int local = iseq->body->local_table_size;
+    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, param, local);
+}
+
+static VALUE
+vm_call_iseq_setup_kwparm_nokwarg(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)
+{
+    VM_ASSERT((ci->flag & VM_CALL_KWARG) == 0);
+    const rb_iseq_t *iseq = def_iseq_ptr(cc->me->def);
+    const struct rb_iseq_param_keyword *kw_param = iseq->body->param.keyword;
+    VALUE * const argv = cfp->sp - calling->argc;
+    VALUE * const klocals = argv + kw_param->bits_start - kw_param->num;
+
+    for (int i=0; i<kw_param->num; i++) {
+        klocals[i] = kw_param->default_values[i];
+    }
+    /* NOTE: don't need to setup (clear) unspecified bits
+             because no code check it.
+             klocals[kw_param->num] = INT2FIX(0); */
+
+    int param = iseq->body->param.size;
+    int local = iseq->body->local_table_size;
+    return vm_call_iseq_setup_normal(ec, cfp, calling, cc->me, 0, param, 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)
@@ -1809,6 +1872,43 @@ vm_callee_setup_arg(rb_execution_context https://github.com/ruby/ruby/blob/trunk/vm_insnhelper.c#L1872
             }
             return (int)iseq->body->param.opt_table[opt];
         }
+        else if (rb_iseq_only_kwparam_p(iseq) && !IS_ARGS_SPLAT(ci)) {
+            const int lead_num = iseq->body->param.lead_num;
+            const int argc = calling->argc;
+            const struct rb_iseq_param_keyword *kw_param = iseq->body->param.keyword;
+
+            if (ci->flag & VM_CALL_KWARG) {
+                const struct rb_call_info_kw_arg *kw_arg = ((struct rb_call_info_with_kwarg *)ci)->kw_arg;
+
+                if (argc - kw_arg->keyword_len == lead_num) {
+                    const int ci_kw_len = kw_arg->keyword_len;
+                    const VALUE * const ci_keywords = kw_arg->keywords;
+                    VALUE * const ci_kws = ALLOCA_N(VALUE, ci_kw_len);
+                    MEMCPY(ci_kws, argv + lead_num, VALUE, ci_kw_len);
+
+                    VALUE *const klocals = argv + kw_param->bits_start - kw_param->num;
+                    args_setup_kw_parameters(ec, iseq, ci_kws, ci_kw_len, ci_keywords, klocals);
+
+                    CC_SET_FASTPATH(cc, vm_call_iseq_setup_kwparm_kwarg,
+                                    !(METHOD_ENTRY_VISI(cc->me) == METHOD_VISI_PROTECTED));
+
+                    return 0;
+                }
+            }
+            else if (argc == lead_num) {
+                /* no kwarg */
+                VALUE *const klocals = argv + kw_param->bits_start - kw_param->num;
+                args_setup_kw_parameters(ec, iseq, NULL, 0, NULL, klocals);
+
+                if (klocals[kw_param->num] == INT2FIX(0)) {
+                    /* copy from default_values */
+                    CC_SET_FASTPATH(cc, vm_call_iseq_setup_kwparm_nokwarg,
+                                    !(METHOD_ENTRY_VISI(cc->me) == METHOD_VISI_PROTECTED));
+                }
+
+                return 0;
+            }
+        }
     }
 
     return setup_parameters_complex(ec, iseq, calling, ci, argv, arg_setup_method);

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

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