Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

동결 및 동결은 언제 사용합니까? 루비에서

요즘은 #freeze를 보는 것이 일반적입니다. 루비 코드에서 사용됩니다. 그러나 왜 동결이 사용되는지 완전히 명확하지 않은 경우가 많습니다. 이 게시물에서는 개발자가 변수를 고정할 수 있는 가장 일반적인 이유를 살펴보겠습니다. 각 이유를 설명하기 위해 Rails 코드베이스 및 기타 인기 있는 오픈 소스 프로젝트에서 예제 코드를 발췌했습니다.

불변 상수 만들기

Ruby에서 상수는 변경 가능합니다. 조금 복잡하지만 코드는 이해하기 쉽습니다. 여기에서 문자열 상수를 만들고 여기에 다른 문자열을 추가합니다.

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

#freeze를 사용하여 실제로 일정한 상수를 만들 수 있습니다. 이번에는 문자열을 수정하려고 하면 RuntimeError가 발생합니다.

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

다음은 ActionDispatch 코드베이스의 실제 예입니다. Rails는 민감한 데이터를 "[FILTERED]" 텍스트로 대체하여 로그에서 숨깁니다. 이 텍스트는 고정된 상수에 저장됩니다.

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

객체 할당 줄이기

Ruby 앱의 속도를 높이기 위해 할 수 있는 가장 좋은 방법 중 하나는 생성되는 객체의 수를 줄이는 것입니다. 객체 할당의 성가신 소스 중 하나는 대부분의 앱에 흩어져 있는 문자열 리터럴에서 비롯됩니다.

log("foobar")와 같은 메소드 호출을 수행할 때마다 새로운 String 객체를 생성합니다. 코드가 이와 같은 메서드를 초당 수천 번 호출하면 초당 수천 개의 문자열을 생성(및 가비지 수집)한다는 의미입니다. 많은 오버헤드가 발생합니다!

다행히 Ruby는 우리에게 탈출구를 제공합니다. 문자열 리터럴을 고정하면 Ruby 인터프리터는 하나의 String 객체만 생성하고 나중에 사용할 수 있도록 캐시합니다. 고정된 문자열 인수와 고정되지 않은 문자열 인수의 성능을 보여주는 빠른 벤치마크를 작성했습니다. 약 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

Rails 라우터를 보면 작동하는 것을 볼 수 있습니다. 라우터는 모든 웹 페이지 요청에 사용되므로 속도가 빨라야 합니다. 이는 고정된 문자열 리터럴이 많다는 것을 의미합니다.

# 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

Ruby의 내장 최적화>=2.2

Ruby 2.2 이상(MRI)은 해시 키로 사용되는 문자열 리터럴을 자동으로 고정합니다.

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

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

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

그리고 Matz에 따르면 모든 문자열 리터럴은 Ruby 3에서 자동으로 고정됩니다.

가치 개체 및 기능 프로그래밍

Ruby는 함수형 프로그래밍 언어가 아니지만 많은 Rubyists는 함수형 스타일로 작업하는 것의 가치를 보기 시작했습니다. 이 스타일의 주요 교리 중 하나는 부작용을 피해야 한다는 것입니다. 개체는 초기화된 후에 변경되어서는 안 됩니다.

생성자 내에서 freeze 메서드를 호출하면 객체가 절대 변경되지 않을 것임을 보장할 수 있습니다. 의도하지 않은 부작용으로 인해 예외가 발생합니다.

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