Add a specialized instruction for `.nil?` calls

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 <kivikakk@github.com>
This commit is contained in:
Aaron Patterson 2019-06-04 16:29:08 -07:00
Родитель 117241b3c7
Коммит 9faef3113f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 953170BCB4FFAFC6
8 изменённых файлов: 48 добавлений и 1 удалений

9
benchmark/nil_p.yml Normal file
Просмотреть файл

@ -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

Просмотреть файл

@ -3255,6 +3255,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;
}

Просмотреть файл

@ -3,6 +3,7 @@ firstline, predefined = __LINE__+1, %[\
max
min
freeze
nil?
inspect
intern
object_id

Просмотреть файл

@ -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)

Просмотреть файл

@ -1687,7 +1687,7 @@ rb_true(VALUE obj)
*/
static VALUE
VALUE
rb_false(VALUE obj)
{
return Qfalse;

1
vm.c
Просмотреть файл

@ -1656,6 +1656,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
}

Просмотреть файл

@ -550,6 +550,7 @@ enum ruby_basic_operators {
BOP_LENGTH,
BOP_SIZE,
BOP_EMPTY_P,
BOP_NIL_P,
BOP_SUCC,
BOP_GT,
BOP_GE,

Просмотреть файл

@ -4228,6 +4228,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)
{