obj.nil? vs. obj == nil

¿Es mejor usar obj.nil? u obj == nil y obj == nil son los beneficios de ambos?

¿Es mejor usar obj.nil? u obj == nil

Es exactamente lo mismo Tiene exactamente los mismos efectos observables desde el exterior (pfff) *

y cuáles son los beneficios de ambos.

Si te gustan las micro optimizaciones, todos los objetos devolverán false a .nil? mensaje a excepción del objeto nil sí mismo, mientras que el objeto que utiliza el mensaje == realizará una pequeña microcomparación con el otro objeto para determinar si es el mismo objeto.

* Ver comentarios.

Personalmente, prefiero object.nil? ya que puede ser menos confuso en líneas más largas; sin embargo, también suelo usar object.blank? si estoy trabajando en Rails ya que también comprueba si la variable está vacía.

En muchos casos, tampoco, solo prueba el valor de verdad booleano

Aunque las dos operaciones son muy diferentes, estoy bastante seguro de que siempre producirán el mismo resultado, al menos hasta que alguien al borde de algo decida anular la #nil? del #nil? método. (Uno llama al método #nil? de Object o anulado en NilClass y uno se compara con el nil singleton).

Sugeriría que, en caso de duda, vaya por un tercer camino, en realidad, y simplemente pruebe el valor de verdad de una expresión.

Entonces, if x y no if x == nil o if x.nil? , para tener esta prueba DTRT cuando el valor de expresión es falso . Trabajar de esta manera también puede ayudar a evitar tentar a alguien a definir FalseClass#nil? como verdadero

Dejando a un lado la syntax y el estilo, quería ver cómo eran “los mismos” varios enfoques para las pruebas de nada. Entonces, escribí algunos puntos de referencia para ver y lancé varias formas de prueba nula.

TL; DR – Resultados primero

Los resultados reales mostraron que el uso de obj como nil check es el más rápido en todos los casos. obj es consistentemente más rápido en un 30% o más que comprobar obj.nil? .

Sorprendentemente, el obj comporta entre 3 y 4 veces más rápido que las variaciones en obj == nil , por lo que parece haber una penalización de rendimiento castigadora.

¿Desea acelerar su algoritmo de alto rendimiento en un 200% -300%? Convierta todos los obj == null verificaciones obj == null en obj . ¿Quieres cargar el rendimiento de tu código? Use obj == null donde sea que pueda. (Es una broma: ¡no saques tu código!).

En el análisis final, siempre use obj . Eso está de acuerdo con la regla de Ruby Style Guide : no haga comprobaciones explícitas que no sean nulas, a menos que se trate de valores booleanos.

Las condiciones de referencia

OK, entonces esos son los resultados, entonces, ¿cómo se arma este punto de referencia, qué pruebas se realizaron y cuáles son los detalles de los resultados?

Los controles nulos que surgieron son:

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

Escogí varios tipos de Ruby para probar, en caso de que los resultados cambiaran según el tipo. Estos tipos fueron Fixnum, Float, FalseClass, TrueClass, String y Regex

Usé estas condiciones de verificación nula en cada uno de los tipos para ver si había una diferencia entre ellos, en cuanto al rendimiento. Para cada tipo, probé objetos nulos y no valores (por ejemplo, 1_000_000 , 100_000.0 , false , true , "string" , y /\w/ ) para ver si hay una diferencia en la comprobación de nulo en un objeto eso es nil versus en un objeto que no es nulo.

Los puntos de referencia

Con todo eso fuera del camino, aquí está el código de referencia:

 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 

Los resultados

Los resultados fueron interesantes, porque el rendimiento de los tipos Fixnum, Float y String fue prácticamente idéntico, Regex casi así, y FalseClass y TrueClass funcionaron mucho más rápido. Las pruebas se realizaron en las versiones de MRI 1.9.3, 2.0.0, 2.1.5 y 2.2.5 con resultados comparativos muy similares en todas las versiones. Los resultados de la versión de MRI 2.2.5 se muestran aquí (y están disponibles en la esencia :

 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) 

Puede usar Symbol # to_proc on nil? , mientras que no sería práctico en 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 

¿No estoy usando .nil? en absoluto cuando puedes hacer:

 unless obj // do work end 

En realidad es más lento con .nil? pero no perceptiblemente. .nil? es solo un método para verificar si ese objeto es igual a cero, aparte del atractivo visual y muy poco rendimiento que se necesita, no hay diferencia.

Algunos podrían sugerir que usar .nil? es más lento que la simple comparación, lo cual tiene sentido cuando lo piensas.

Pero si la escala y la velocidad no son su problema, entonces .nil? es quizás más legible