[ruby-core:94033] [Ruby master Feature#15902] Add a specialized instruction for `.nil?`

From: ko1@...
Date: 2019-07-30 07:45:25 UTC
List: ruby-core #94033
Issue #15902 has been updated by ko1 (Koichi Sasada).


All right, go ahead.

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

* 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 instruction 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 = 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
```

### 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 (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 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 (ア 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
```

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 might 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 = nil, Object.new
  niller = Niller.new
benchmark:
  - xnil.nil?
  - notnil.nil?
  - niller.nil?
loop_count: 10000000
```

### Results (tested against master @ c9b74f9fd95113df903fc34cc1d6ec3fb3160c85 )

```
[aaron@TC ~/g/ruby (specialized-nilp)]$ make benchmark ARGS=benchmark/nil_p.yml
./revision.h unchanged
/Users/aaron/.rbenv/shims/ruby --disable=gems -rrubygems -I./benchmark/lib ./benchmark/benchmark-driver/exe/benchmark-driver \
	            --executables="compare-ruby::/Users/aaron/.rbenv/shims/ruby --disable=gems -I.ext/common --disable-gem" \
	            --executables="built-ruby::./miniruby -I./lib -I. -I.ext/common  ./tool/runruby.rb --extout=.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=benchmark/nil_p.yml
./revision.h unchanged
/Users/aaron/.rbenv/shims/ruby --disable=gems -rrubygems -I./benchmark/lib ./benchmark/benchmark-driver/exe/benchmark-driver \
	            --executables="compare-ruby::/Users/aaron/.rbenv/shims/ruby --disable=gems -I.ext/common --disable-gem" \
	            --executables="built-ruby::./miniruby -I./lib -I. -I.ext/common  ./tool/runruby.rb --extout=.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=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>

In This Thread

Prev Next