Ruby의 OpenStruct에 대한 대안

9145 단어 rubyrails
이번 주에 이전 Rails 애플리케이션에서 Rubocop을 업데이트했습니다. 그리고 그것은 Rubocop이 내 얼굴에 새로운 규칙을 던진다는 것을 의미합니다. 그 새로운 규칙 중 하나는 지금 분명히 안티 패턴으로 간주되는 OpenStruct를 사용하는 것에 관한 것입니다. OpenStruct에 대한 대안을 위한 시간입니다.
OpenStruct를 사용하면 메서드를 통해 키에 액세스할 수 있는 일종의 해시를 만드는 빠른 방법입니다. 그러나 OpenStruct에는 큰 단점이 있습니다. thisthis 소스에 따르면 성능에 대한 우려가 있습니다. 루보캅says :

Instantiation of an OpenStruct invalidates Ruby global method cache as it causes dynamic method definition during program runtime. This could have an effect on performance, especially in case of single-threaded applications with multiple OpenStruct instantiations.



따라서 OpenStruct를 사용하는 것이 대부분의 경우 반패턴이라면 OpenStruct에 대한 좋은 대안은 무엇이며 Ruby 3.1.2에서 이를 어떻게 벤치마킹합니까?

우선: using OpenStruct이 유효한 경우가 있습니다. 한 번만 실행되는 Ruby 스크립트를 만드시겠습니까? 자신의 재량에 따라 OpenStruct를 사용하고 실행하고 스크립트를 버리십시오. 매우 자주 사용되는 Rails 애플리케이션에서 OpenStruct를 사용합니까? 그렇게 하지 말고 아래의 대안을 확인하십시오. 내 요점은: 인터넷에서 나와 같은 임의의 사람이 각각의 모든 경우에 해야 할 일을 말하도록 하지 마십시오.

OpenStruct


OpenStruct 에 대한 간단한 복습부터 시작하겠습니다.

require 'ostruct'

car = OpenStruct.new
car.wheels = 4
car.mileage = 13_337

puts "My car has {car.wheels} wheels and" 
puts "a mileage of #{car.mileage} miles"
# => My car has 4 wheels and 
# => a mileage of 13337 miles


아주 멋진. 구문은 매우 깨끗하고 역동적이며 작업하기 쉽습니다. 이제 250,000번 해봅시다.

require 'benchmark'
require 'ostruct'

cars = []
time = Benchmark.measure do
  250_000.times do 
    car = OpenStruct.new
    car.wheel = 4
    car.mileage = rand((1..100_000))
    cars << car
  end
end

puts time
# => 6.662331   0.400225   7.062556 (  7.062667)


7.06초가 걸렸다. 몇 가지 대안을 계속 살펴보겠습니다.

수업



좋은 노인 class . 필요할 때 항상 Ruby에 있습니다. 위OpenStruct에 대한 대안으로 고객 클래스를 사용하면 다음과 같습니다.

class Car
  attr_accessor :wheels, :mileage
end

cars = []
time = Benchmark.measure do
  250_000.times do
    car = Car.new
    car.wheels = 4
    car.mileage = rand((1..100_000))
    cars << car
  end
end

puts time
# => 0.091187   0.000000   0.091187 (  0.101195)


이번에는 0.10초가 걸렸다. OpenStruct 를 사용하는 것보다 70배 빠릅니다.
class를 사용하는 것이 좋은 대안입니다. 더 많은 코드가 필요하지만 괜찮습니다. 그리고 이 경우 class를 사용하면 비즈니스 도메인의 이 부분이 어떻게 생겼는지 알 수 있습니다.

구조체



물론 Struct도 살펴봐야 합니다.

car_struct = Struct.new(:wheels, :mileage)
cars = []

time = Benchmark.measure do
  250_000.times do
    cars << car_struct.new(4, rand((1..100_000)))
  end
end

puts time
# => 0.085577   0.000649   0.086226 (  0.086235)

Struct의 경우 class를 사용하는 것과 유사하게 0.09초가 걸렸습니다.

해시시



마지막 대안은 일반 해시입니다.

cars = []
time = Benchmark.measure do
  250_000.times do
    cars << { wheels: 4, mileage: rand((1..100_000)) }
  end
end

puts time
# => 0.085295   0.000000   0.085295 (  0.085301)


그리고 해시는 실행 시간이 0.09초인 class 또는 Struct를 사용하는 것과 유사합니다.

기준



요약:

OpenStruct   - 6.66 0.40  7.06 (7.06) - baseline
Custom class - 0.09 0.00  0.09 (0.10) - ~70 times faster
Struct       - 0.08 0.00  0.08 (0.08) - ~80 times faster
Hash         - 0.08 0.00  0.08 (0.08) - ~80 times faster


무엇을 선택해야 합니까?


OpenStruct의 위의 대안 중 하나가 좋습니다. 성능의 마지막 몇 퍼센트를 짜내고 싶다면 Struct 또는 해시를 사용하십시오. 코드의 특정 지점에서만 데이터를 사용하는 경우 해시도 적절합니다. 그러나 해당 데이터가 코드의 여러 경로를 통과하는 즉시 해시를 사용자 지정 class 또는 Struct 로 업그레이드하는 것을 두려워하지 마십시오. Aclass는 데이터 모델의 모양을 더 잘 전달하고 결국 이를 적용할 수 있으므로 더 탄력적입니다. 여기서 잘못된 선택을 할 수 없으며 나중에 언제든지 데이터 구조를 업그레이드할 수 있습니다. Ruby는 그런 면에서 훌륭합니다.

좋은 웹페이지 즐겨찾기