5.6 KiB
Ruby 최적화 및 메타프로그래밍
Ruby는 강력한 동적 언어로서 코드를 간결하게 유지하면서도 유연한 프로그래밍이 가능하다. 하지만 동적 특성으로 인해 성능이 저하될 수 있으며, 이를 해결하기 위한 최적화 기법이 필요하다.
또한, Ruby는 메타프로그래밍을 통해 코드를 동적으로 생성하고 수정할 수 있는 강력한 기능을 제공한다. 이 글에서는 Ruby 성능 최적화 방법과 메타프로그래밍 활용법을 정리한다.
1. Ruby 성능 최적화
Ruby는 생산성이 높은 언어지만 속도는 C나 Java보다 느릴 수 있다. 따라서 Ruby 코드의 성능을 최적화하는 것이 중요하다.
1.1. 불필요한 객체 생성을 줄이기
Ruby에서는 모든 것이 객체이므로, 불필요한 객체 생성을 피하는 것이 중요하다.
(1) 문자열 객체 재사용
String 객체를 반복 생성하면 메모리를 낭비할 수 있다.
비효율적인 코드:
100_000.times do
str = "Hello" # 새로운 String 객체 생성
end
효율적인 코드:
100_000.times do
str = "Hello".freeze # 동일 객체 재사용
end
.freeze를 사용하면 객체가 변경되지 않도록 고정되어 메모리 사용량을 줄일 수 있다.
1.2. each 대신 map과 reduce 활용
반복문(each)을 사용하면 불필요한 루프가 발생할 수 있다. map과 reduce를 사용하면 코드의 가독성이 향상되고 성능이 개선된다.
비효율적인 코드:
result = []
[1, 2, 3, 4, 5].each do |num|
result << num * 2
end
효율적인 코드:
result = [1, 2, 3, 4, 5].map { |num| num * 2 }
1.3. symbol과 hash 활용
Ruby에서 Symbol은 String보다 가볍고 성능이 좋다.
# String 기반 키 사용 (비효율적)
hash = { "name" => "Alice", "age" => 25 }
# Symbol 기반 키 사용 (효율적)
hash = { name: "Alice", age: 25 }
Symbol은 불변 객체이므로, 동일한 값이 여러 번 생성되지 않아 성능이 향상된다.
1.4. lazy를 활용한 지연 평가
lazy를 사용하면 모든 데이터를 한 번에 로드하지 않고, 필요한 만큼만 계산할 수 있다.
numbers = (1..Float::INFINITY).lazy.map { |n| n * 2 }
puts numbers.first(5) # [2, 4, 6, 8, 10]만 생성됨
1.5. 메모이제이션(Memoization) 활용
반복적으로 계산해야 하는 값은 한 번만 계산하고 저장하는 것이 좋다.
def slow_method
@result ||= expensive_computation
end
def expensive_computation
sleep(2)
"Computed!"
end
puts slow_method # 첫 실행은 2초 대기
puts slow_method # 두 번째 실행은 즉시 반환
@result ||= expensive_computation을 사용하면 한 번만 계산하고 이후에는 캐시된 값을 반환한다.
2. 메타프로그래밍
메타프로그래밍이란 코드를 실행 중에 변경하거나 새로운 메서드를 동적으로 정의하는 기법이다.
2.1. define_method를 활용한 동적 메서드 생성
define_method를 사용하면 반복적인 메서드 정의를 줄이고 코드의 유연성을 높일 수 있다.
class User
[:name, :age, :email].each do |attribute|
define_method(attribute) do
instance_variable_get("@#{attribute}")
end
define_method("#{attribute}=") do |value|
instance_variable_set("@#{attribute}", value)
end
end
end
user = User.new
user.name = "Alice"
puts user.name # Alice
이렇게 하면 setter/getter 메서드를 자동으로 생성할 수 있다.
2.2. method_missing을 활용한 동적 메서드 처리
정의되지 않은 메서드가 호출될 때 method_missing을 활용하면 유연한 처리가 가능하다.
class DynamicMethod
def method_missing(name, *args)
puts "#{name} 메서드가 호출됨 (args: #{args})"
end
end
obj = DynamicMethod.new
obj.hello(1, 2, 3) # hello 메서드가 호출됨 (args: [1, 2, 3])
method_missing을 이용하면 일일이 메서드를 정의하지 않고도 유연하게 동작을 처리할 수 있다.
2.3. class_eval과 instance_eval을 활용한 클래스 동적 수정
class Person
end
Person.class_eval do
def greet
"Hello!"
end
end
puts Person.new.greet # Hello!
class_eval을 사용하면 기존 클래스에 동적으로 메서드를 추가할 수 있다.
2.4. self.included을 활용한 모듈 자동 확장
module AutoMethods
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def say_hello
"Hello from #{self}"
end
end
end
class MyClass
include AutoMethods
end
puts MyClass.say_hello # Hello from MyClass
모듈을 include하면 자동으로 클래스 메서드가 추가되도록 설정할 수 있다.
3. 결론
Ruby는 동적인 특성을 활용하여 성능 최적화와 메타프로그래밍이 가능하다.
| 주제 | 주요 내용 |
|---|---|
| 최적화 | 객체 생성을 줄이고, map, symbol, lazy 등을 활용 |
| 메타프로그래밍 | define_method, method_missing, class_eval을 활용한 동적 코드 작성 |
최적화를 통해 성능을 개선하고, 메타프로그래밍을 활용하여 유지보수가 쉬운 코드를 작성하는 것이 핵심이다.