开发者

obj.nil? vs. obj == nil

开发者_JS百科Is it better to use obj.nil? or obj == nil and what are the benefits of both?


Is it better to use obj.nil? or obj == nil

It is exactly the same. It has the exact same observable effects from the outside ( pfff ) *

and what are the benefits of both.

If you like micro optimizations all the objects will return false to the .nil? message except for the object nil itself, while the object using the == message will perform a tiny micro comparison with the other object to determine if it is the same object.

* See comments.


Syntax and style aside, I wanted to see how "the same" various approaches to testing for nil were. So, I wrote some benchmarks to see, and threw various forms of nil testing at it.

TL;DR - Results First

The actual results showed that using obj as a nil check is the fastest in all cases. obj is consistently faster by 30% or more than checking obj.nil?.

Surprisingly, obj performs about 3-4 times as fast as variations on obj == nil, for which there seems to be a punishing performance penalty.

Want to speed up your performance-intensive algorithm by 200%-300%? Convert all obj == nil checks to obj. Want to sandbag your code's performance? Use obj == nil everywhere that you can. (just kidding: don't sandbag your code!).

In the final analysis, always use obj. That jives with the Ruby Style Guide rule: Don't do explicit non-nil checks unless you're dealing with boolean values.

The Benchmark Conditions

OK, those are the results. So how is this benchmark put together, what tests were done, and what are the details of the results?

The nil checks that I came up with are:

  • obj
  • obj.nil?
  • !obj
  • !!obj
  • obj == nil
  • obj != nil

I picked various Ruby types to test, in case the results changed based on the type. These types were Fixnum, Float, FalseClass, TrueClass, String, and Regex.

I used these nil check conditions on each of the types to see if there was a difference between them, performance-wise. For each type, I tested both nil objects and non-nil value objects (e.g. 1_000_000, 100_000.0, false, true, "string", and /\w/) to see if there's a difference in checking for nil on an object that is nil versus on an object that's not nil.

The Benchmarks

With all of that out of the way, here is the benchmark code:

require 'benchmark'

nil_obj = nil
N = 10_000_000

puts RUBY_DESCRIPTION

[1_000_000, 100_000.0, false, true, "string", /\w/].each do |obj|
  title = "#{obj} (#{obj.class.name})"
  puts "============================================================"
  puts "Running tests for obj = #{title}"

  Benchmark.bm(15, title) do |x|
    implicit_obj_report   = x.report("obj:")            { N.times { obj            } }
    implicit_nil_report   = x.report("nil_obj:")        { N.times { nil_obj        } }
    explicit_obj_report   = x.report("obj.nil?:")       { N.times { obj.nil?       } }
    explicit_nil_report   = x.report("nil_obj.nil?:")   { N.times { nil_obj.nil?   } }
    not_obj_report        = x.report("!obj:")           { N.times { !obj           } }
    not_nil_report        = x.report("!nil_obj:")       { N.times { !nil_obj       } }
    not_not_obj_report    = x.report("!!obj:")          { N.times { !!obj          } }
    not_not_nil_report    = x.report("!!nil_obj:")      { N.times { !!nil_obj      } }
    equals_obj_report     = x.report("obj == nil:")     { N.times { obj == nil     } }
    equals_nil_report     = x.report("nil_obj == nil:") { N.times { nil_obj == nil } }
    not_equals_obj_report = x.report("obj != nil:")     { N.times { obj != nil     } }
    not_equals_nil_report = x.report("nil_obj != nil:") { N.times { nil_obj != nil } }
  end
end

The Results

The results were interesting, because Fixnum, Float, and String types performance was virtually identical, Regex nearly so, and FalseClass and TrueClass performed much more quickly. Testing was done on MRI versions 1.9.3, 2.0.0, 2.1.5, and 2.2.5 with very similar comparative results across the versions. The results from the MRI 2.2.5 version are shown here (and available in the gist:

ruby 2.2.5p319 (2016-04-26 revision 54774) [x86_64-darwin14]
============================================================
Running tests for obj = 1000000 (Fixnum)
                      user     system      total        real
obj:              0.970000   0.000000   0.970000 (  0.987204)
nil_obj:          0.980000   0.010000   0.990000 (  0.980796)
obj.nil?:         1.250000   0.000000   1.250000 (  1.268564)
nil_obj.nil?:     1.280000   0.000000   1.280000 (  1.287800)
!obj:             1.050000   0.000000   1.050000 (  1.064061)
!nil_obj:         1.070000   0.000000   1.070000 (  1.170393)
!!obj:            1.110000   0.000000   1.110000 (  1.122204)
!!nil_obj:        1.120000   0.000000   1.120000 (  1.147679)
obj == nil:       2.110000   0.000000   2.110000 (  2.137807)
nil_obj == nil:   1.150000   0.000000   1.150000 (  1.158301)
obj != nil:       2.980000   0.010000   2.990000 (  3.041131)
nil_obj != nil:   1.170000   0.000000   1.170000 (  1.203015)
============================================================
Running tests for obj = 100000.0 (Float)
                      user     system      total        real
obj:              0.940000   0.000000   0.940000 (  0.947136)
nil_obj:          0.950000   0.000000   0.950000 (  0.986488)
obj.nil?:         1.260000   0.000000   1.260000 (  1.264953)
nil_obj.nil?:     1.280000   0.000000   1.280000 (  1.306817)
!obj:             1.050000   0.000000   1.050000 (  1.058924)
!nil_obj:         1.070000   0.000000   1.070000 (  1.096747)
!!obj:            1.100000   0.000000   1.100000 (  1.105708)
!!nil_obj:        1.120000   0.010000   1.130000 (  1.132248)
obj == nil:       2.140000   0.000000   2.140000 (  2.159595)
nil_obj == nil:   1.130000   0.000000   1.130000 (  1.151257)
obj != nil:       3.010000   0.000000   3.010000 (  3.042263)
nil_obj != nil:   1.170000   0.000000   1.170000 (  1.189145)
============================================================
Running tests for obj = false (FalseClass)
                      user     system      total        real
obj:              0.930000   0.000000   0.930000 (  0.933712)
nil_obj:          0.950000   0.000000   0.950000 (  0.973776)
obj.nil?:         1.250000   0.000000   1.250000 (  1.340943)
nil_obj.nil?:     1.270000   0.010000   1.280000 (  1.282267)
!obj:             1.030000   0.000000   1.030000 (  1.039532)
!nil_obj:         1.060000   0.000000   1.060000 (  1.068765)
!!obj:            1.100000   0.000000   1.100000 (  1.111930)
!!nil_obj:        1.110000   0.000000   1.110000 (  1.115355)
obj == nil:       1.110000   0.000000   1.110000 (  1.121403)
nil_obj == nil:   1.100000   0.000000   1.100000 (  1.114550)
obj != nil:       1.190000   0.000000   1.190000 (  1.207389)
nil_obj != nil:   1.140000   0.000000   1.140000 (  1.181232)
============================================================
Running tests for obj = true (TrueClass)
                      user     system      total        real
obj:              0.960000   0.000000   0.960000 (  0.964583)
nil_obj:          0.970000   0.000000   0.970000 (  0.977366)
obj.nil?:         1.260000   0.000000   1.260000 (  1.265229)
nil_obj.nil?:     1.270000   0.010000   1.280000 (  1.283342)
!obj:             1.040000   0.000000   1.040000 (  1.059689)
!nil_obj:         1.070000   0.000000   1.070000 (  1.068290)
!!obj:            1.120000   0.000000   1.120000 (  1.154803)
!!nil_obj:        1.130000   0.000000   1.130000 (  1.155932)
obj == nil:       1.100000   0.000000   1.100000 (  1.102394)
nil_obj == nil:   1.130000   0.000000   1.130000 (  1.160324)
obj != nil:       1.190000   0.000000   1.190000 (  1.202544)
nil_obj != nil:   1.200000   0.000000   1.200000 (  1.200812)
============================================================
Running tests for obj = string (String)
                      user     system      total        real
obj:              0.940000   0.000000   0.940000 (  0.953357)
nil_obj:          0.960000   0.000000   0.960000 (  0.962029)
obj.nil?:         1.290000   0.010000   1.300000 (  1.306233)
nil_obj.nil?:     1.240000   0.000000   1.240000 (  1.243312)
!obj:             1.030000   0.000000   1.030000 (  1.046630)
!nil_obj:         1.060000   0.000000   1.060000 (  1.123925)
!!obj:            1.130000   0.000000   1.130000 (  1.144168)
!!nil_obj:        1.130000   0.000000   1.130000 (  1.147330)
obj == nil:       2.320000   0.000000   2.320000 (  2.341705)
nil_obj == nil:   1.100000   0.000000   1.100000 (  1.118905)
obj != nil:       3.040000   0.010000   3.050000 (  3.057040)
nil_obj != nil:   1.150000   0.000000   1.150000 (  1.162085)
============================================================
Running tests for obj = (?-mix:\w) (Regexp)
                      user     system      total        real
obj:              0.930000   0.000000   0.930000 (  0.939815)
nil_obj:          0.960000   0.000000   0.960000 (  0.961852)
obj.nil?:         1.270000   0.000000   1.270000 (  1.284321)
nil_obj.nil?:     1.260000   0.000000   1.260000 (  1.275042)
!obj:             1.040000   0.000000   1.040000 (  1.042543)
!nil_obj:         1.040000   0.000000   1.040000 (  1.047280)
!!obj:            1.120000   0.000000   1.120000 (  1.128137)
!!nil_obj:        1.130000   0.000000   1.130000 (  1.138988)
obj == nil:       1.520000   0.010000   1.530000 (  1.529547)
nil_obj == nil:   1.110000   0.000000   1.110000 (  1.125693)
obj != nil:       2.210000   0.000000   2.210000 (  2.226783)
nil_obj != nil:   1.170000   0.000000   1.170000 (  1.169347)


Personally, I prefer object.nil? as it can be less confusing on longer lines; however, I also usually use object.blank? if I'm working in Rails as that also checks to see if the variable is empty.


In many cases, neither, just test the boolean truth value

Although the two operations are very different I'm pretty sure they will always produce the same result, at least until someone out on the edge of something decides to override Object's #nil? method. (One calls the #nil? method inherited from Object or overridden in NilClass and one compares against the nil singleton.)

I would suggest that when in doubt you go a third way, actually, and just test the truth value of an expression.

So, if x and not if x == nil or if x.nil?, in order to have this test DTRT when the expression value is false. Working this way may also help to avoiding tempting someone to define FalseClass#nil? as true.


You can use Symbol#to_proc on nil?, whereas it wouldn't be practical on x == nil.

arr = [1, 2, 3]
arr.any?(&:nil?) # Can be done
arr.any?{|x| x == nil} # More verbose, and I suspect is slower on Ruby 1.9.2
! arr.all? # Check if any values are nil or false


I find myself not using .nil? at all when you can do:

unless obj
  // do work
end

It's actually slower using .nil? but not noticeably. .nil? is just a method to check if that object is equal to nil, other than the visual appeal and very little performance it takes there is no difference.


Some might suggest that using .nil? is slower than the simple comparison, which makes sense when you think about it.

But if scale and speed are not your concern, then .nil? is perhaps more readable.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜