From 43b6fe94007525ec1fa424da57b86d8d5c624ff7 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 14 May 2020 12:28:31 -0700 Subject: [PATCH] Speed up setting instance variables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch speeds up setting instance variables somewhat. Here is the benchmark: ```ruby class IVSetter def init @a = @b = @c = @d = nil end end obj = IVSetter.new require "benchmark/ips" Benchmark.ips do |x| x.report("init") do obj.init end end ``` Before this patch: ``` [aaron@tc-lan-adapter ~/g/ruby (master)]$ ./ruby -v ../y.rb ruby 2.8.0dev (2020-05-13T15:51:04Z master 65c5a39578) [x86_64-darwin19] Warming up -------------------------------------- init 1.324M i/100ms Calculating ------------------------------------- init 13.034M (± 1.6%) i/s - 66.210M in 5.080905s ``` After this patch: ``` [aaron@tc-lan-adapter ~/g/ruby (faster-ivar-set)]$ ./ruby -v ../y.rb ruby 2.8.0dev (2020-05-14T19:33:13Z faster-ivar-set 60cfbb9d7e) [x86_64-darwin19] Warming up -------------------------------------- init 1.456M i/100ms Calculating ------------------------------------- init 14.599M (± 1.5%) i/s - 74.270M in 5.088467s ``` This speeds up ivar sets because we can reduce redundant checks. `ROBJECT_NUMIV` and `ROBJECT_IVPTR` both check that the object is a `T_OBJECT`, but the caller (`vm_setivar`) already knows this is a `T_OBJECT` (otherwise this branch wouldn't be executing). Secondly, both `ROBJECT_IVPTR` and `ROBJECT_NUMIV` check flags for `ROBJECT_EMBED`. We can reduce the check to just once. I thought the C compiler would be smart enough to eliminate these redundant checks, but after reading the assembly code it's clear that the checks are being performed multiple times. --- vm_insnhelper.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 4d1d4ea0f9a15a..6796c0b7f8c552 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -1142,10 +1142,21 @@ vm_setivar(VALUE obj, ID id, VALUE val, IVC ic, const struct rb_callcache *cc, i if (LIKELY( (!is_attr && RB_DEBUG_COUNTER_INC_UNLESS(ivar_set_ic_miss_serial, ic->ic_serial == RCLASS_SERIAL(klass))) || ( is_attr && RB_DEBUG_COUNTER_INC_UNLESS(ivar_set_ic_miss_unset, vm_cc_attr_index(cc) > 0)))) { - VALUE *ptr = ROBJECT_IVPTR(obj); + VALUE *ptr; + uint32_t numiv; + struct RObject *const robj = ROBJECT(obj); + + if (RB_FL_ANY_RAW(obj, ROBJECT_EMBED)) { + ptr = robj->as.ary; + numiv = ROBJECT_EMBED_LEN_MAX; + } else { + ptr = robj->as.heap.ivptr; + numiv = robj->as.heap.numiv; + } + index = !is_attr ? ic->index : vm_cc_attr_index(cc)-1; - if (RB_DEBUG_COUNTER_INC_UNLESS(ivar_set_ic_miss_oorange, index < ROBJECT_NUMIV(obj))) { + if (RB_DEBUG_COUNTER_INC_UNLESS(ivar_set_ic_miss_oorange, index < numiv)) { RB_OBJ_WRITE(obj, &ptr[index], val); RB_DEBUG_COUNTER_INC(ivar_set_ic_hit); return val; /* inline cache hit */