diff options
author | Jeremy Evans <code@jeremyevans.net> | 2023-04-01 20:55:43 -0700 |
---|---|---|
committer | Jeremy Evans <code@jeremyevans.net> | 2023-04-25 08:06:16 -0700 |
commit | 9b4bf02aa89fa9a9a568b7be045ab1df8053f0e6 (patch) | |
tree | a356df2ef8dfd20f1604cdb23eb3aca07ad7b1e8 /vm_insnhelper.c | |
parent | af2da6419aba1e242e851664b4e6816aeb27f8cb (diff) |
Optimize send calls
Similar to the bmethod optimization, this avoids using
CALLER_ARG_SPLAT if not necessary. As long as the method argument
can be shifted off, other arguments are passed through as-is.
This optimizes the following types of calls:
* send(meth, arg) ~5%
* send(meth, *args) ~75% for args.length == 200
* send(meth, *args, **kw) ~50% for args.length == 200
* send(meth, **kw) ~25%
* send(meth, kw: 1) ~115%
Note that empty argument splats do get slower with this approach,
by about 20%. This is probably because iseq argument setup is
slower for empty argument splats than CALLER_SETUP_ARG is. Other
than non-empty argument splats, other argument splats are faster,
with the speedup depending on the number of arguments.
The following types of calls are not optimized:
* send(*args)
* send(*args, **kw)
This is because the you cannot shift the method argument off
without first splatting the arg.
Notes
Notes:
Merged: https://github.com/ruby/ruby/pull/7522
Diffstat (limited to 'vm_insnhelper.c')
-rw-r--r-- | vm_insnhelper.c | 107 |
1 files changed, 74 insertions, 33 deletions
diff --git a/vm_insnhelper.c b/vm_insnhelper.c index d66f1e833a..35f864e20d 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -3813,14 +3813,13 @@ vm_call_symbol(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, const struct rb_callinfo *ci, VALUE symbol, int flags) { ASSUME(calling->argc >= 0); - /* Also assumes CALLER_SETUP_ARG is already done. */ enum method_missing_reason missing_reason = MISSING_NOENTRY; int argc = calling->argc; VALUE recv = calling->recv; VALUE klass = CLASS_OF(recv); ID mid = rb_check_id(&symbol); - flags |= VM_CALL_OPT_SEND | (calling->kw_splat ? VM_CALL_KW_SPLAT : 0); + flags |= VM_CALL_OPT_SEND; if (UNLIKELY(! mid)) { mid = idMethodMissing; @@ -3910,14 +3909,52 @@ vm_call_symbol(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, } static VALUE -vm_call_opt_send(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling) +vm_call_opt_send0(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, int flags) { - RB_DEBUG_COUNTER_INC(ccf_opt_send); + const struct rb_callinfo *ci = calling->ci; + int i; + VALUE sym; + + i = calling->argc - 1; + + if (calling->argc == 0) { + rb_raise(rb_eArgError, "no method name given"); + } + + sym = TOPN(i); + /* E.g. when i == 2 + * + * | | | | TOPN + * +------+ | | + * | arg1 | ---+ | | 0 + * +------+ | +------+ + * | arg0 | -+ +-> | arg1 | 1 + * +------+ | +------+ + * | sym | +---> | arg0 | 2 + * +------+ +------+ + * | recv | | recv | 3 + * --+------+--------+------+------ + */ + /* shift arguments */ + if (i > 0) { + MEMMOVE(&TOPN(i), &TOPN(i-1), VALUE, i); + } + calling->argc -= 1; + DEC_SP(1); + + return vm_call_symbol(ec, reg_cfp, calling, ci, sym, flags); +} - int i, flags = VM_CALL_FCALL; - VALUE sym, argv_ary; +static VALUE +vm_call_opt_send_complex(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling) +{ + RB_DEBUG_COUNTER_INC(ccf_opt_send_complex); + const struct rb_callinfo *ci = calling->ci; + int flags = VM_CALL_FCALL; + VALUE sym; - CALLER_SETUP_ARG(reg_cfp, calling, calling->ci, ALLOW_HEAP_ARGV); + VALUE argv_ary; + CALLER_SETUP_ARG(reg_cfp, calling, ci, ALLOW_HEAP_ARGV); if (UNLIKELY(argv_ary = calling->heap_argv)) { sym = rb_ary_shift(argv_ary); flags |= VM_CALL_ARGS_SPLAT; @@ -3926,37 +3963,38 @@ vm_call_opt_send(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct ((struct RHash *)last_hash)->basic.flags |= RHASH_PASS_AS_KEYWORDS; calling->kw_splat = 0; } + return vm_call_symbol(ec, reg_cfp, calling, ci, sym, flags); } - else { - i = calling->argc - 1; - if (calling->argc == 0) { - rb_raise(rb_eArgError, "no method name given"); - } + if (calling->kw_splat) flags |= VM_CALL_KW_SPLAT; + return vm_call_opt_send0(ec, reg_cfp, calling, flags); +} - sym = TOPN(i); - /* E.g. when i == 2 - * - * | | | | TOPN - * +------+ | | - * | arg1 | ---+ | | 0 - * +------+ | +------+ - * | arg0 | -+ +-> | arg1 | 1 - * +------+ | +------+ - * | sym | +---> | arg0 | 2 - * +------+ +------+ - * | recv | | recv | 3 - * --+------+--------+------+------ - */ - /* shift arguments */ - if (i > 0) { - MEMMOVE(&TOPN(i), &TOPN(i-1), VALUE, i); - } - calling->argc -= 1; - DEC_SP(1); +static VALUE +vm_call_opt_send_simple(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling) +{ + RB_DEBUG_COUNTER_INC(ccf_opt_send_simple); + return vm_call_opt_send0(ec, reg_cfp, calling, vm_ci_flag(calling->ci) | VM_CALL_FCALL); +} + +static VALUE +vm_call_opt_send(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling) +{ + RB_DEBUG_COUNTER_INC(ccf_opt_send); + + const struct rb_callinfo *ci = calling->ci; + int flags = vm_ci_flag(ci); + + if (UNLIKELY(!(flags & VM_CALL_ARGS_SIMPLE) && + ((calling->argc == 1 && (flags & (VM_CALL_ARGS_SPLAT | VM_CALL_KW_SPLAT))) || + (calling->argc == 2 && (flags & VM_CALL_ARGS_SPLAT) && (flags & VM_CALL_KW_SPLAT)) || + ((flags & VM_CALL_KWARG) && (vm_ci_kwarg(ci)->keyword_len == calling->argc))))) { + CC_SET_FASTPATH(calling->cc, vm_call_opt_send_complex, TRUE); + return vm_call_opt_send_complex(ec, reg_cfp, calling); } - return vm_call_symbol(ec, reg_cfp, calling, calling->ci, sym, flags); + CC_SET_FASTPATH(calling->cc, vm_call_opt_send_simple, TRUE); + return vm_call_opt_send_simple(ec, reg_cfp, calling); } static VALUE @@ -4819,6 +4857,9 @@ vm_invoke_symbol_block(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, } calling->recv = TOPN(--calling->argc); } + if (calling->kw_splat) { + flags |= VM_CALL_KW_SPLAT; + } return vm_call_symbol(ec, reg_cfp, calling, ci, symbol, flags); } |