obj.nil? vs. obj == nil

前端 未结 7 821
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-11-29 22:08

Is it better to use obj.nil? or obj == nil and what are the benefits of both?

相关标签:
7条回答
  • 2020-11-29 22:25

    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)
    
    0 讨论(0)
  • 2020-11-29 22:27

    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 讨论(0)
  • 2020-11-29 22:33

    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.

    0 讨论(0)
  • 2020-11-29 22:34

    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.

    0 讨论(0)
  • 2020-11-29 22:38

    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.

    0 讨论(0)
  • 2020-11-29 22:44

    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.

    0 讨论(0)
提交回复
热议问题