12/08/2018, 17:06

So sánh giữa nil? và == nil

Có điểm gì khác biệt giữa nil? và == nil Sẽ không có gì khác biệt khi mà bạn nhìn vào kết quả trả về. Và tôi thích dùng nil? vì nó dễ đọc hơn. Nhưng đó chỉ là vấn đề về cảm nhận của mỗi người Tuy nhiên có một sự khác biệt nhỏ trong cách tính kết quả này. 1. nil? nil? là một phương thức được ...

Có điểm gì khác biệt giữa nil? và == nil

Sẽ không có gì khác biệt khi mà bạn nhìn vào kết quả trả về. Và tôi thích dùng nil? vì nó dễ đọc hơn. Nhưng đó chỉ là vấn đề về cảm nhận của mỗi người

Tuy nhiên có một sự khác biệt nhỏ trong cách tính kết quả này.

1. nil?

nil? là một phương thức được định nghĩa trên Object và NilClass.

# NilClass
rb_true(VALUE obj)
{
  return Qtrue;
}

# Object
static VALUE
rb_false(VALUE obj)
{
  return Qfalse;
}

Kiểm tra nil? chỉ là việc gọi một phương thức đơn giản. Đối tượng của bạn kế thừa từ class Object đã được cài đặt phương thức nil? và luôn trả về false

Object.new.nil? # return false 

Lưu ý: Trong lớp cha của lớp Object là lớp BasicObject, phương thức nil? không được cài đặt

BasicObject.new.nil? # undefined method `nil?' for #<BasicObject:0x000000065e7940>

2. == nil

a == b chỉ là cú pháp để gửi thông điệp == từ phải sang trái cùng với một đối số duy nhất. Điều đó có nghĩa là a.==(b) đối với trường hợp tổng quát. Còn trong trường hợp cụ thể của chúng ta a.==(nil)

== là một phương thức được định nghĩa trên BasicObject:

rb_obj_equal(VALUE obj1, VALUE obj2)
{
    if (obj1 == obj2) return Qtrue;
    return Qfalse;
}

Từ tài liệu

Ở mức đối tượng, == trả về true chỉ khi giá trị so sánh và giá trị được so sánh có cùng một đối tượng

Vì vậy kết quả trả về true nếu hai biến đang trỏ đến cùng một đối tượng hoặc nếu lớp nhận đã ghi đè phương thức == theo cách khác. Và các lớp con được dùng để ghi đè == để thực hiện hành vi cụ thể của lớp. Điều đó có nghĩa là hiệu suất phụ thuộc vào việc thực hiện ==. Không giống như nil? không được ghi đè bằng các lớp con.

Hơi khó hiểu nhỉ. Để tôi giải thích điều này

a = :framgia
b = :framgia
a == b # return true
a.object_id # return 9107228
b.object_id # return 9107228

c = "framgia"
d = "framgia"
c == d # return true
c.object_id # return 53695100
d.object_id # return 38048440

Cả 2 phép so sánh đều trả về true. Nhưng a, b cùng thuộc 1 đối tượng vì có cùng object_id. Còn c, d không thuộc cùng một đối tượng vì ko cùng object_id, tức là c == d đã ghi đè phương thức ==

3. Giải pháp khác

Trong ruby, để kiểm tra mọi thứ người ta hay đem ra so sánh với false hoặc nil.Nhưng bạn cũng có thể bỏ qua việc so sánh này bằng cách kiểm tra chính đối tượng mà bạn đang dùng

if some_object
  puts "some_object is neither nil nor false" # Một vài đối tượng không phải nil cũng chẳng phải false
end

4. Hiệu năng

Tôi mong ước nil? có thể nhanh được như == nil. Bởi vì hiệu suất việc ghi đè == phụ thuộc vào receiver. Đây là một benchmark đơn giản để kiểm tra các giả định của tôi. Như thường lệ tôi sử dụng gem benchmark/ips của Evan Phoenix.

require 'benchmark/ips'

class ExpensiveEquals
  def ==(other)
    1000.times {}
    super(other)
  end
end

string = 'something'
number = 123
expensive = ExpensiveEquals.new
notnil = Object.new
isnil = nil

Benchmark.ips do |x|

  x.report('isnil.nil?') do
    if isnil.nil?
    end
  end

  x.report('notnil.nil?') do
    if notnil.nil?
    end
  end

  x.report('string.nil?') do
    if string.nil?
    end
  end

  x.report('number.nil?') do
    if number.nil?
    end
  end

  x.report('expensive.nil?') do
    if expensive.nil?
    end
  end

  x.report('isnil == nil') do
    if isnil == nil
    end
  end

  x.report('notnil == nil') do
    if notnil == nil
    end
  end

  x.report('string == nil') do
    if string == nil
    end
  end

  x.report('number == nil') do
    if number == nil
    end
  end

  x.report('expensive == nil') do
    if expensive == nil
    end
  end

  x.report('nil == isnil') do
    if nil == isnil
    end
  end

  x.report('nil == notnil') do
    if nil == notnil
    end
  end

  x.report('nil == string') do
    if nil == string
    end
  end

  x.report('nil == number') do
    if nil == number
    end
  end

  x.report('nil == expensive') do
    if nil == expensive
    end
  end


  x.report('isnil') do
    if isnil
    end
  end

  x.report('notnil') do
    if notnil
    end
  end

  x.report('string') do
    if string
    end
  end

  x.report('number') do
    if number
    end
  end

  x.report('expensive') do
    if expensive
    end
  end

  x.compare!
end

Kết quả trả về:

notnil: 11840655.4 i/s
expensive: 11801608.9 i/s - 1.00x slower
number: 11679119.8 i/s - 1.01x slower
string: 11598016.4 i/s - 1.02x slower
isnil: 11529034.6 i/s - 1.03x slower
notnil == nil: 10397262.7 i/s - 1.14x slower
nil == expensive: 10319767.0 i/s - 1.15x slower
nil == string: 10188393.9 i/s - 1.16x slower
nil == number: 10167930.4 i/s - 1.16x slower
isnil == nil: 10120560.0 i/s - 1.17x slower
nil == notnil: 10069779.1 i/s - 1.18x slower
nil == isnil: 10055165.2 i/s - 1.18x slower
expensive.nil?:  9970905.5 i/s - 1.19x slower
string.nil?:  9967045.3 i/s - 1.19x slower
number.nil?:  9893974.5 i/s - 1.20x slower
notnil.nil?:  9581974.5 i/s - 1.24x slower
isnil.nil?:  8390963.6 i/s - 1.41x slower
string == nil:  6915330.1 i/s - 1.71x slower
number == nil:  6803969.3 i/s - 1.74x slower
expensive == nil:    25382.6 i/s - 466.49x slower

chú thích: i/s là iterations/second

Theo như kết quả thì phép toán chạy chậm nhất expensive == nil chậm gấp 466.49 lần phép toán chạy nhanh nhất notnil (notnil được lấy ra làm đơn vị cơ bản để so sánh slower với các phép toán còn lại)

Tôi không mong muốn nil? lại chạy chậm đến vậy. Qua kết qua thu được thì việc kiểm tra nil == sẽ cho hiệu năng cao là bạn kiểm tra == nil. Và trong các phép kiểm tra thì kiểm tra chính đối tượng như được nêu ra ở phần 3. giải pháp khác đã đạt được hiệu năng cao nhất

5. Kết luận

Bài viết này mình dịch từ nguồn: http://pascalbetz.github.io/ruby/2016/05/17/nilvsequals/

Mong nhận được sự đóng góp ý kiến của các bạn

0