Actions
Feature #15902
closedAdd a specialized instruction for `.nil?`
Status:
Closed
Assignee:
-
Target version:
-
Description
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¶
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:
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
Actions
Like0
Like0Like0Like0Like0Like0Like0