Computer >> Máy Tính >  >> Lập trình >> Ruby

Khi nào sử dụng đông lạnh và đông lạnh? trong Ruby

Ngày nay, việc thấy #freeze khá phổ biến được sử dụng trong mã Ruby. Nhưng nó thường không hoàn toàn rõ ràng TẠI SAO đông lạnh được sử dụng. Trong bài đăng này, chúng ta sẽ xem xét các lý do phổ biến nhất mà một nhà phát triển có thể đóng băng các biến. Để minh họa cho từng lý do, tôi đã trích đoạn mã ví dụ từ cơ sở mã Rails và các dự án nguồn mở phổ biến khác.

Tạo hằng bất biến

Trong Ruby, các hằng số có thể thay đổi được. Nó hơi khó hiểu, nhưng mã đủ dễ hiểu. Ở đây, tôi đã tạo một hằng số chuỗi và đang nối một chuỗi khác vào nó.

MY_CONSTANT = "foo"
MY_CONSTANT << "bar"
puts MY_CONSTANT.inspect # => "foobar"

Bằng cách sử dụng #freeze, tôi có thể tạo một hằng số thực sự không đổi. Lần này, khi tôi cố gắng sửa đổi chuỗi, tôi nhận được lỗi RuntimeError.

MY_CONSTANT = "foo".freeze
MY_CONSTANT << "bar" # => RuntimeError: can't modify frozen string

Đây là một ví dụ thực tế về điều này trong cơ sở mã ActionDispatch. Rails ẩn dữ liệu nhạy cảm trong nhật ký bằng cách thay thế nó bằng văn bản "[FILTERED]". Văn bản này được lưu trữ trong một hằng số cố định.

module ActionDispatch
  module Http
    class ParameterFilter
      FILTERED = '[FILTERED]'.freeze
      ...

Giảm phân bổ đối tượng

Một trong những điều tốt nhất bạn có thể làm để tăng tốc ứng dụng Ruby của mình là giảm số lượng đối tượng được tạo. Một nguồn phân bổ đối tượng khó chịu đến từ các ký tự chuỗi được rải trong hầu hết các ứng dụng.

Mỗi khi bạn thực hiện một cuộc gọi phương thức như log ("foobar"), bạn tạo một đối tượng Chuỗi mới. Nếu mã của bạn gọi một phương thức như thế này hàng nghìn lần mỗi giây, điều đó có nghĩa là bạn đang tạo (và thu gom rác) hàng nghìn chuỗi mỗi giây. Đó là rất nhiều chi phí!

May mắn thay, Ruby cho chúng ta một lối thoát. Nếu chúng ta đóng băng các ký tự chuỗi, trình thông dịch Ruby sẽ chỉ tạo một đối tượng Chuỗi và sẽ lưu vào bộ nhớ cache để sử dụng trong tương lai. Tôi đã tập hợp một điểm chuẩn nhanh cho thấy hiệu suất của các đối số chuỗi được cố định và không được cố định. Nó cho thấy hiệu suất tăng khoảng 50%.

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

Bạn có thể thấy điều này đang hoạt động nếu bạn nhìn vào bộ định tuyến Rails. Vì bộ định tuyến được sử dụng cho mọi yêu cầu trang web nên nó cần phải nhanh. Điều đó có nghĩa là rất nhiều ký tự chuỗi cố định.

# excerpted from https://github.com/rails/rails/blob/f91439d848b305a9d8f83c10905e5012180ffa28/actionpack/lib/action_dispatch/journey/router/utils.rb#L15
def self.normalize_path(path)
  path = "/#{path}"
  path.squeeze!('/'.freeze)
  path.sub!(%r{/+\Z}, ''.freeze)
  path.gsub!(/(%[a-f0-9]{2})/) { $1.upcase }
  path = '/' if path == ''.freeze
  path
end

Các tính năng tối ưu hóa tích hợp trong Ruby> =2.2

Ruby 2.2 trở lên (MRI) sẽ tự động đóng băng các ký tự chuỗi được sử dụng làm khóa băm.

user = {"name" => "george"}

# In Ruby >= 2.2
user["name"]

# ...is equivalent to this, in Ruby <= 2.1
user["name".freeze]

Và theo Matz, tất cả các ký tự chuỗi sẽ được đóng băng tự động trong Ruby 3.

Đối tượng giá trị &lập trình chức năng

Mặc dù Ruby không phải là một ngôn ngữ lập trình chức năng, nhưng nhiều người theo chủ nghĩa Ruby đã bắt đầu thấy giá trị của việc làm việc theo phong cách chức năng. Một trong những nguyên lý chính của phong cách này là bạn nên tránh các tác dụng phụ. Các đối tượng không bao giờ được thay đổi sau khi chúng được khởi tạo.

Bằng cách gọi phương thức đóng băng bên trong hàm tạo, có thể đảm bảo rằng một đối tượng sẽ không bao giờ thay đổi. Mọi tác dụng phụ không chủ ý sẽ dẫn đến một ngoại lệ được nêu ra.

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