Ruby Hash[key] So sánh :symbol và “string”

Ai sẽ quan tâm?

Gần đây có một cuộc thảo luận trên kênh Trailblazer Gitter về Hashes như một params, làm thế nào để gán chúng, và như thường lệ một cuộc tranh luận bùng nổ và nó đã tạo ra một cuộc thi đo lường: cách nào tốt hơn và nhanh hơn.

Đối với những người thiếu kiên nhẫn: về hash nhỏ sẽ không có gì quan trọng; về hash lớn: symbol nhanh hơn string khá nhiều trong khi frozen string xấp xỉ symbol.

Cách tốt nhất để tranh luận, là đưa ra các bằng chứng. Vì vậy, tôi có viết 1 đoạn code nhỏ để so sánh. Cụ thể ở đây (Github).

Lượt 1: Hash[:symbol] với Hash[“string”]

Đầu tiên, tạo ra các hash theo cách của Ruby

require "benchmark/ips"

def symbol_key
  {symbol: 42}
end

def symbol_key_arrow
  {:symbol => 42}
end

def symbol_key_in_string_form
  {'sym_str': 42}
end

def string_key_arrow_double_quotes
  {"string" => 42}
end

def string_key_arrow_single_quotes
  {'string' => 42}
end

Toàn bộ code của phần này có sẵn trên Github, các bạn có thể thử trên máy của mình. Kết quả trên đây được ghi nhận từ máy tính của tôi với cấu hình sau:

Comparison:
      {symbol: 42}:  3068310.2 i/s
   {'sym_str': 42}:  3154171.0 i/s  - same-ish
   {:symbol => 42}:  3107735.4 i/s - same-ish
  {'string' => 42}:  2997402.4 i/s - 1.05x  slower
  {"string" => 42}:  2981802.5 i/s - 1.06x  slower

Dù lặp tới gần 3 triệu vòng/giây nhưng cũng chỉ chậm hơn 5 - 6% nên sẽ chẳng ai quan tâm đến việc này phải không?

string keys không làm giảm tốc độ ứng dụng của bạn.

Lượt 2: Với những hash lớn thì sao?

Cùng thử với 1000 cặp key - value. Bạn có thể xem code cụ thể phần này ở Github.

require "benchmark/ips"


STRING_KEYS = (1..1000).map{"x" "key_#{x}"}.shuffle
FROZEN_KEYS = STRING_KEYS.map{x "fr_#{x}".freeze}
SYMBOL_KEYS = STRING_KEYS.map(&:to_sym)

def symbol_hash
  SYMBOL_KEYS.collect { "k" [ k, rand(1..100)]}.to_h
end

def string_hash
  STRING_KEYS.collect { k [ k, rand(1..100)]}.to_h
end

def frozen_hash
  FROZEN_KEYS.collect { "k" [ k, rand(1..100)]}.to_h
end


SYMBOL_HASH = symbol_hash
STRING_HASH = string_hash
FROZEN_HASH = frozen_hash


def reading_symbol_hash
  SYMBOL_HASH[SYMBOL_KEYS.sample]
end

def reading_string_hash
  STRING_HASH[STRING_KEYS.sample]
end

def reading_frozen_hash
  FROZEN_HASH[FROZEN_KEYS.sample]
end

Cùng xem kết quả:

Creating large Hash

Comparison:
  Symbol Keys:  3243.5 i/s
  Frozen Keys:  3022.1 i/s - 1.07x  slower
  String Keys:  2671.3 i/s - 1.21x  slower

-----------------------------------------------------------------

Reading large Hash

Comparison:
  Symbol Keys:  5930853.6 i/s
  Frozen Keys:  5546042.9 i/s - 1.07x  slower
  String Keys:  5029193.7 i/s - 1.18x  slower

Tổng kết

Như vậy các bạn có thể thấy sự khác biệt khá hớn với 1000 cặp key - value. Nếu bạn thực sự có những hash rất lớn, symbol key có thể giúp bạn cải thiện tốc độ ứng dụng. Nhưng theo tôi, bạn hãy tiếp dục dùng cách mà bạn đang dùng vì nếu string key khiến bạn code dễ dàng hơn, hãy tiếp tục dùng nó, bởi bạn có thể sẽ gặp nhiều vấn đề khi chuyển chúng sang symbol đó.


Tài liệu tham khảo: https://blog.gorbikoff.com/ruby-hashkey-showdown-symbol-vs-string/