Issue #15902 has been reported by tenderlovemaking (Aaron Patterson).

----------------------------------------
Feature #15902: Add a specialized instruction for `.nil?`
https://bugs.ruby-lang.org/issues/15902

* Author: tenderlovemaking (Aaron Patterson)
* Status: Open
* Priority: Normal
* Assignee: =

* Target version: =

----------------------------------------
I'd like to add a specialized instruction for `.nil?`.  We have specialized=
 instructions for `.length` and `.empty?`, and surprisingly our application=
 also calls `.nil?` a lot:

```
[aaron@TC ~/g/github (gc-boot-stats)]$ git grep '.empty?' | wc -l
    2553
[aaron@TC ~/g/github (gc-boot-stats)]$ git grep '.length' | wc -l
    3975
[aaron@TC ~/g/github (gc-boot-stats)]$ git grep '.nil?' | wc -l
    3117
```

I'm not sure how hot any of the `.nil?` callsites are, but I think this ins=
truction will speed up most of them.

I tried two benchmark runners:

## Benchmark/ips

``` ruby
require "benchmark/ips"

class Niller
  def nil?; true; end
end

not_nil =3D Object.new
xnil =3D nil
niller =3D Niller.new

Benchmark.ips do |x|
  x.report("nil?")    { xnil.nil? }
  x.report("not nil") { not_nil.nil? }
  x.report("niller")   { niller.nil? }
end
```

### Results

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 (=B1 8.1%) i/s -    100.002M in   5.002794s
             not nil     20.046M (=B1 7.6%) i/s -     99.839M in   5.020086s
              niller     22.467M (=B1 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 (=B1 8.8%) i/s -     99.375M in   5.020458s
             not nil     20.529M (=B1 7.0%) i/s -    102.385M in   5.020689s
              niller     21.796M (=B1 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 (=B112.2%) i/s -     98.519M in   5.008817s
             not nil     20.614M (=B1 8.0%) i/s -    102.280M in   5.004531s
              niller     22.223M (=B1 8.8%) i/s -    110.309M in   5.013106s

```

On this patch:

```
[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 (=B1 7.8%) i/s -    138.169M in   5.001730s
             not nil     26.417M (=B1 8.7%) i/s -    131.020M in   5.011674s
              niller     21.561M (=B1 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 (=B1 7.3%) i/s -    139.837M in   5.016590s
             not nil     25.789M (=B112.9%) i/s -    126.470M in   5.011144s
              niller     20.002M (=B112.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 (=B1 8.0%) i/s -    133.755M in   5.013915s
             not nil     25.319M (=B1 7.9%) i/s -    125.713M in   5.007758s
              niller     19.569M (=B111.8%) i/s -     96.286M in   5.008533s
```

According to benchmark/ips, it's about 27% faster when the object is nil or=
 a regular object.  When it's an object that implements .nil?, I think it m=
ight be slower but it's hard to tell.

## Benchmark-driver

I added a benchmark driver file:

``` yaml
prelude: |
  class Niller; def nil?; true; end; end
  xnil, notnil =3D nil, Object.new
  niller =3D Niller.new
benchmark:
  - xnil.nil?
  - notnil.nil?
  - niller.nil?
loop_count: 10000000
```

### Results (tested against master @ c9b74f9fd95113df903fc34cc1d6ec3fb3160c=
85 )

```
[aaron@TC ~/g/ruby (specialized-nilp)]$ make benchmark ARGS=3Dbenchmark/nil=
_p.yml
./revision.h unchanged
/Users/aaron/.rbenv/shims/ruby --disable=3Dgems -rrubygems -I./benchmark/li=
b ./benchmark/benchmark-driver/exe/benchmark-driver \
	            --executables=3D"compare-ruby::/Users/aaron/.rbenv/shims/ruby =
--disable=3Dgems -I.ext/common --disable-gem" \
	            --executables=3D"built-ruby::./miniruby -I./lib -I. -I.ext/com=
mon  ./tool/runruby.rb --extout=3D.ext  -- --disable-gems --disable-gem" \
	            benchmark/nil_p.yml =

Calculating -------------------------------------
                     compare-ruby  built-ruby =

           xnil.nil?      68.825M    405.121M i/s -     10.000M times in 0.=
145296s 0.024684s
         notnil.nil?      66.357M    267.874M i/s -     10.000M times in 0.=
150700s 0.037331s
         niller.nil?     110.273M    123.089M i/s -     10.000M times in 0.=
090684s 0.081242s

Comparison:
                        xnil.nil?
          built-ruby: 405120725.0 i/s =

        compare-ruby:  68825019.2 i/s - 5.89x  slower

                      notnil.nil?
          built-ruby: 267873885.2 i/s =

        compare-ruby:  66357000.6 i/s - 4.04x  slower

                      niller.nil?
          built-ruby: 123089042.6 i/s =

        compare-ruby: 110273035.8 i/s - 1.12x  slower

[aaron@TC ~/g/ruby (specialized-nilp)]$ make benchmark ARGS=3Dbenchmark/nil=
_p.yml
./revision.h unchanged
/Users/aaron/.rbenv/shims/ruby --disable=3Dgems -rrubygems -I./benchmark/li=
b ./benchmark/benchmark-driver/exe/benchmark-driver \
	            --executables=3D"compare-ruby::/Users/aaron/.rbenv/shims/ruby =
--disable=3Dgems -I.ext/common --disable-gem" \
	            --executables=3D"built-ruby::./miniruby -I./lib -I. -I.ext/com=
mon  ./tool/runruby.rb --extout=3D.ext  -- --disable-gems --disable-gem" \
	            benchmark/nil_p.yml =

Calculating -------------------------------------
                     compare-ruby  built-ruby =

           xnil.nil?      45.083M    360.998M i/s -     10.000M times in 0.=
221811s 0.027701s
         notnil.nil?      69.558M    271.054M i/s -     10.000M times in 0.=
143765s 0.036893s
         niller.nil?     115.423M     79.667M i/s -     10.000M times in 0.=
086638s 0.125523s

Comparison:
                        xnil.nil?
          built-ruby: 360997801.1 i/s =

        compare-ruby:  45083426.9 i/s - 8.01x  slower

                      notnil.nil?
          built-ruby: 271054130.3 i/s =

        compare-ruby:  69557959.1 i/s - 3.90x  slower

                      niller.nil?
        compare-ruby: 115422793.6 i/s =

          built-ruby:  79666674.5 i/s - 1.45x  slower

```

I think there is too much noise for the third case.

I'm not happy about making `rb_false` non-static, but I'm not sure how else=
 to do this patch.

What do you think?

---Files--------------------------------
0001-Add-a-specialized-instruction-for-.nil-calls.patch (7.13 KB)


-- =

https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request / ruby-lang.org?subject=3Dunsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>