From 069d0a691702d4473591788b935bda9bc96f9399 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 4 Jun 2019 16:29:08 -0700 Subject: [PATCH] Add a specialized instruction for `.nil?` calls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds a specialized instruction for called to `.nil?`. It is about 27% faster than master in the case where the object is nil or not nil. In the case where an object implements `nil?`, I think it may be slightly slower. Here is a benchmark: ```ruby require "benchmark/ips" class Niller def nil?; true; end end not_nil = Object.new xnil = nil niller = Niller.new Benchmark.ips do |x| x.report("nil?") { xnil.nil? } x.report("not nil") { not_nil.nil? } x.report("niller") { niller.nil? } end ``` On Ruby master: ``` [aaron@TC ~/g/ruby (master)]$ ./ruby compil.rb Warming up -------------------------------------- nil? 429.195k i/100ms not nil 437.889k i/100ms niller 437.935k i/100ms Calculating ------------------------------------- nil? 20.166M (± 8.1%) i/s - 100.002M in 5.002794s not nil 20.046M (± 7.6%) i/s - 99.839M in 5.020086s niller 22.467M (± 6.1%) i/s - 112.111M in 5.013817s [aaron@TC ~/g/ruby (master)]$ ./ruby compil.rb Warming up -------------------------------------- nil? 449.660k i/100ms not nil 433.836k i/100ms niller 443.073k i/100ms Calculating ------------------------------------- nil? 19.997M (± 8.8%) i/s - 99.375M in 5.020458s not nil 20.529M (± 7.0%) i/s - 102.385M in 5.020689s niller 21.796M (± 8.0%) i/s - 108.110M in 5.002300s [aaron@TC ~/g/ruby (master)]$ ./ruby compil.rb Warming up -------------------------------------- nil? 402.119k i/100ms not nil 438.968k i/100ms niller 398.226k i/100ms Calculating ------------------------------------- nil? 20.050M (±12.2%) i/s - 98.519M in 5.008817s not nil 20.614M (± 8.0%) i/s - 102.280M in 5.004531s niller 22.223M (± 8.8%) i/s - 110.309M in 5.013106s ``` On this branch: ``` [aaron@TC ~/g/ruby (specialized-nilp)]$ ./ruby compil.rb Warming up -------------------------------------- nil? 468.371k i/100ms not nil 456.517k i/100ms niller 454.981k i/100ms Calculating ------------------------------------- nil? 27.849M (± 7.8%) i/s - 138.169M in 5.001730s not nil 26.417M (± 8.7%) i/s - 131.020M in 5.011674s niller 21.561M (± 7.5%) i/s - 107.376M in 5.018113s [aaron@TC ~/g/ruby (specialized-nilp)]$ ./ruby compil.rb Warming up -------------------------------------- nil? 477.259k i/100ms not nil 428.712k i/100ms niller 446.109k i/100ms Calculating ------------------------------------- nil? 28.071M (± 7.3%) i/s - 139.837M in 5.016590s not nil 25.789M (±12.9%) i/s - 126.470M in 5.011144s niller 20.002M (±12.2%) i/s - 98.144M in 5.001737s [aaron@TC ~/g/ruby (specialized-nilp)]$ ./ruby compil.rb Warming up -------------------------------------- nil? 467.676k i/100ms not nil 445.791k i/100ms niller 415.024k i/100ms Calculating ------------------------------------- nil? 26.907M (± 8.0%) i/s - 133.755M in 5.013915s not nil 25.319M (± 7.9%) i/s - 125.713M in 5.007758s niller 19.569M (±11.8%) i/s - 96.286M in 5.008533s ``` Co-Authored-By: Ashe Connor --- benchmark/nil_p.yml | 9 +++++++++ compile.c | 1 + defs/id.def | 1 + insns.def | 14 ++++++++++++++ object.c | 2 +- vm.c | 1 + vm_core.h | 1 + vm_insnhelper.c | 20 ++++++++++++++++++++ 8 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 benchmark/nil_p.yml diff --git a/benchmark/nil_p.yml b/benchmark/nil_p.yml new file mode 100644 index 0000000000..79ba4f2177 --- /dev/null +++ b/benchmark/nil_p.yml @@ -0,0 +1,9 @@ +prelude: | + class Niller; def nil?; true; end; end + xnil, notnil = nil, Object.new + niller = Niller.new +benchmark: + - xnil.nil? + - notnil.nil? + - niller.nil? +loop_count: 10000000 diff --git a/compile.c b/compile.c index 64bde0c962..fd69bf6683 100644 --- a/compile.c +++ b/compile.c @@ -3251,6 +3251,7 @@ iseq_specialized_instruction(rb_iseq_t *iseq, INSN *iobj) case idLength: SP_INSN(length); return COMPILE_OK; case idSize: SP_INSN(size); return COMPILE_OK; case idEmptyP: SP_INSN(empty_p);return COMPILE_OK; + case idNilP: SP_INSN(nil_p); return COMPILE_OK; case idSucc: SP_INSN(succ); return COMPILE_OK; case idNot: SP_INSN(not); return COMPILE_OK; } diff --git a/defs/id.def b/defs/id.def index 44890cf352..1ab54075b4 100644 --- a/defs/id.def +++ b/defs/id.def @@ -3,6 +3,7 @@ firstline, predefined = __LINE__+1, %[\ max min freeze + nil? inspect intern object_id diff --git a/insns.def b/insns.def index c971026cf6..7c93af6e4d 100644 --- a/insns.def +++ b/insns.def @@ -808,6 +808,20 @@ opt_str_freeze } } +/* optimized nil? */ +DEFINE_INSN +opt_nil_p +(CALL_INFO ci, CALL_CACHE cc) +(VALUE recv) +(VALUE val) +{ + val = vm_opt_nil_p(ci, cc, recv); + + if (val == Qundef) { + CALL_SIMPLE_METHOD(); + } +} + DEFINE_INSN opt_str_uminus (VALUE str, CALL_INFO ci, CALL_CACHE cc) diff --git a/object.c b/object.c index 00a70898f3..a749fd09ab 100644 --- a/object.c +++ b/object.c @@ -1664,7 +1664,7 @@ rb_true(VALUE obj) */ -static VALUE +VALUE rb_false(VALUE obj) { return Qfalse; diff --git a/vm.c b/vm.c index 70ec2315c1..2c6eb55fdd 100644 --- a/vm.c +++ b/vm.c @@ -1657,6 +1657,7 @@ vm_init_redefined_flag(void) OP(Call, CALL), (C(Proc)); OP(And, AND), (C(Integer)); OP(Or, OR), (C(Integer)); + OP(NilP, NIL_P), (C(NilClass)); #undef C #undef OP } diff --git a/vm_core.h b/vm_core.h index 95cd2d87d9..63489af744 100644 --- a/vm_core.h +++ b/vm_core.h @@ -547,6 +547,7 @@ enum ruby_basic_operators { BOP_LENGTH, BOP_SIZE, BOP_EMPTY_P, + BOP_NIL_P, BOP_SUCC, BOP_GT, BOP_GE, diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 523ac71f42..52c61c1e65 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -4239,6 +4239,26 @@ vm_opt_empty_p(VALUE recv) } } +VALUE rb_false(VALUE obj); + +static VALUE +vm_opt_nil_p(CALL_INFO ci, CALL_CACHE cc, VALUE recv) +{ + if (recv == Qnil) { + if (BASIC_OP_UNREDEFINED_P(BOP_NIL_P, NIL_REDEFINED_OP_FLAG)) { + return Qtrue; + } else { + return Qundef; + } + } else { + if (vm_method_cfunc_is(ci, cc, recv, rb_false)) { + return Qfalse; + } else { + return Qundef; + } + } +} + static VALUE fix_succ(VALUE x) { -- 2.21.0