Khi nào sử dụng freeze trong Ruby
Gần đây việc sử dụng #freeze trong Ruby khá phổ biến, nhưng taị sao lại thế thì điều này chưa được giải thích rõ. Bài viết này sẽ xem xét những lý do phổ biến nhất mà những developer dùng biến freeze. Tạo hằng số Trong Ruby, hằng số có thể thay đổi. Khá lạ đúng không, nhưng ta có thể kiếm chứng ...
Gần đây việc sử dụng #freeze trong Ruby khá phổ biến, nhưng taị sao lại thế thì điều này chưa được giải thích rõ. Bài viết này sẽ xem xét những lý do phổ biến nhất mà những developer dùng biến freeze.
Tạo hằng số
Trong Ruby, hằng số có thể thay đổi. Khá lạ đúng không, nhưng ta có thể kiếm chứng với đoạn code sau.
MY_CONSTANT = "foo" MY_CONSTANT << "bar" puts MY_CONSTANT.inspect # => "foobar"
Nhưng bằng cách sử dụng freeze, ta sẽ tạo được một hằng số thực sự, đó là hằng số không thể thay đổi, và khi cố gắng thay đổi nó ta sẽ nhận được thông báo lỗi như thế này.
MY_CONSTANT = "foo".freeze MY_CONSTANT << "bar" # => RuntimeError: can't modify frozen string
Giảm việc cấp phát đối tượng
Một trong những cách tốt nhất để tăng tốc ứng dụng Ruby đó là giảm số lượng các object được tạo ra. Một trong những nguồn gây ra việc cấp phát đối tượng này đến từ các string được rải khắp trong ứng dụng của bạn.
Mỗi lần bạn sử dụng method log("foobar"), bạn sẽ tạo một đối tượng string. Nếu code của bạn gọi nó hàng nghìn lần 1 giây, có nghĩa là bạn đang tạo hàng nghìn string 1 giây.
May mắn thay, nếu chúng ta dùng freeze với các string này, Ruby sẽ chỉ tạo ra một đối tượng string và cache chúng lại cho lần sử dụng sau. Chỉ số benchmark cho thấy hiệu suất khi dùng freeze tăng khoảng 50% so với không dùng.
require 'benchmark/ips' def noop(arg) end Benchmark.ips do |x| x.report("normal") { noop("foo") } x.report("frozen") { noop("foo".freeze) } end # Results with MRI 2.2.2: # Calculating ------------------------------------- # normal 152.123k i/100ms # frozen 167.474k i/100ms # ------------------------------------------------- # normal 6.158M (± 3.3%) i/s - 30.881M # frozen 9.312M (± 3.5%) i/s - 46.558M
Sự tối ưu hóa trong Ruby >= 2.2
Ruby 2.2 hoặc hơn sẽ tự động freeze string khi sử dụng string làm key của hash
user = {"name" => "george"} # In Ruby >= 2.2 user["name"] # ...is equivalent to this, in Ruby <= 2.1 user["name".freeze]
Và trong Ruby 3 thì rất có thể tất cả string sẽ đều được tự động freeze. https://bugs.ruby-lang.org/issues/11473
Value objects & functional programming
Ruby không phải ngôn ngữ lập trình hàm, nhưng nhiều Rubyists bắt đầu viết theo phong cách của lập trình hàm. Nguyên lý chính của phong cách này là nên tránh các tác động bên ngoài, tức là đối tượng sẽ không thay đổi sau khi được khởi tạo. Bằng cách sử dụng freeze bên trong constructor, ta có thể đảm bảo đối tượng sẽ không thể thay đổi, và mọi tác động bên ngoài sẽ đều báo lỗi.
class Point attr_accessor :x, :y def initialize(x, y) @x = x @y = y freeze end def change @x = 3 end end point = Point.new(1,2) point.change # RuntimeError: can't modify frozen Point
Tham khảo http://blog.honeybadger.io/when-to-use-freeze-and-frozen-in-ruby/