obj.nil? vs. obj == nil

前端 未结 7 897
爱一瞬间的悲伤
爱一瞬间的悲伤 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)
    

提交回复
热议问题