211 lines
5.6 KiB
Markdown
211 lines
5.6 KiB
Markdown
# **Ruby 최적화 및 메타프로그래밍**
|
|
|
|
Ruby는 강력한 동적 언어로서 **코드를 간결하게 유지하면서도 유연한 프로그래밍이 가능**하다. 하지만 동적 특성으로 인해 성능이 저하될 수 있으며, 이를 해결하기 위한 최적화 기법이 필요하다.
|
|
|
|
또한, Ruby는 **메타프로그래밍**을 통해 코드를 동적으로 생성하고 수정할 수 있는 강력한 기능을 제공한다. 이 글에서는 **Ruby 성능 최적화 방법**과 **메타프로그래밍 활용법**을 정리한다.
|
|
|
|
---
|
|
|
|
## **1. Ruby 성능 최적화**
|
|
|
|
Ruby는 생산성이 높은 언어지만 속도는 C나 Java보다 느릴 수 있다. 따라서 Ruby 코드의 성능을 최적화하는 것이 중요하다.
|
|
|
|
### **1.1. 불필요한 객체 생성을 줄이기**
|
|
|
|
Ruby에서는 **모든 것이 객체**이므로, 불필요한 객체 생성을 피하는 것이 중요하다.
|
|
|
|
#### **(1) 문자열 객체 재사용**
|
|
`String` 객체를 반복 생성하면 메모리를 낭비할 수 있다.
|
|
|
|
**비효율적인 코드:**
|
|
```ruby
|
|
100_000.times do
|
|
str = "Hello" # 새로운 String 객체 생성
|
|
end
|
|
```
|
|
|
|
**효율적인 코드:**
|
|
```ruby
|
|
100_000.times do
|
|
str = "Hello".freeze # 동일 객체 재사용
|
|
end
|
|
```
|
|
|
|
`.freeze`를 사용하면 객체가 변경되지 않도록 고정되어 **메모리 사용량을 줄일 수 있다.**
|
|
|
|
---
|
|
|
|
### **1.2. `each` 대신 `map`과 `reduce` 활용**
|
|
|
|
반복문(`each`)을 사용하면 불필요한 루프가 발생할 수 있다. `map`과 `reduce`를 사용하면 코드의 가독성이 향상되고 성능이 개선된다.
|
|
|
|
**비효율적인 코드:**
|
|
```ruby
|
|
result = []
|
|
[1, 2, 3, 4, 5].each do |num|
|
|
result << num * 2
|
|
end
|
|
```
|
|
|
|
**효율적인 코드:**
|
|
```ruby
|
|
result = [1, 2, 3, 4, 5].map { |num| num * 2 }
|
|
```
|
|
|
|
---
|
|
|
|
### **1.3. `symbol`과 `hash` 활용**
|
|
|
|
Ruby에서 `Symbol`은 `String`보다 가볍고 성능이 좋다.
|
|
|
|
```ruby
|
|
# String 기반 키 사용 (비효율적)
|
|
hash = { "name" => "Alice", "age" => 25 }
|
|
|
|
# Symbol 기반 키 사용 (효율적)
|
|
hash = { name: "Alice", age: 25 }
|
|
```
|
|
|
|
**`Symbol`은 불변 객체이므로, 동일한 값이 여러 번 생성되지 않아 성능이 향상된다.**
|
|
|
|
---
|
|
|
|
### **1.4. `lazy`를 활용한 지연 평가**
|
|
|
|
`lazy`를 사용하면 **모든 데이터를 한 번에 로드하지 않고, 필요한 만큼만 계산**할 수 있다.
|
|
|
|
```ruby
|
|
numbers = (1..Float::INFINITY).lazy.map { |n| n * 2 }
|
|
puts numbers.first(5) # [2, 4, 6, 8, 10]만 생성됨
|
|
```
|
|
|
|
---
|
|
|
|
### **1.5. 메모이제이션(Memoization) 활용**
|
|
|
|
반복적으로 계산해야 하는 값은 **한 번만 계산하고 저장**하는 것이 좋다.
|
|
|
|
```ruby
|
|
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`를 사용하면 **반복적인 메서드 정의를 줄이고 코드의 유연성을 높일 수 있다.**
|
|
|
|
```ruby
|
|
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`을 활용하면 유연한 처리가 가능하다.
|
|
|
|
```ruby
|
|
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`을 활용한 클래스 동적 수정**
|
|
|
|
```ruby
|
|
class Person
|
|
end
|
|
|
|
Person.class_eval do
|
|
def greet
|
|
"Hello!"
|
|
end
|
|
end
|
|
|
|
puts Person.new.greet # Hello!
|
|
```
|
|
|
|
`class_eval`을 사용하면 **기존 클래스에 동적으로 메서드를 추가할 수 있다.**
|
|
|
|
---
|
|
|
|
### **2.4. `self.included`을 활용한 모듈 자동 확장**
|
|
|
|
```ruby
|
|
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`을 활용한 동적 코드 작성 |
|
|
|
|
**최적화를 통해 성능을 개선하고, 메타프로그래밍을 활용하여 유지보수가 쉬운 코드를 작성하는 것이 핵심이다.** |