rust zero cost abstractions

wangki·2025년 2월 16일
0

Rust

목록 보기
20/55

개요

추상화는 성능을 희생해야 한다.

이 개념을 깨고, 추상화가 있어도 성능 손실이 없다 는 원칙을 Zero-Cost Abstraction이라고 한다.

rust에서는 고수준 코드를 런타임 오버헤드가 거의 없이 사용할 수 있다.

1. Generic

fn generic_function<T>(t: T) -> T 
where 
    T: std::ops::Mul<Output = T> + Copy
{
    t * t 
} 

fn main() {
    // i32
    let num_i32 = generic_function(9);

    // f32
    let num_f32 = generic_function(9.0);
    

    println!("{}", num_i32);
    println!("{}", num_f32);
}

Monomorphization

rust는 다른 언어와 다르게 컴파일 타임에 모든 제네릭 타입에 대해서 정확한 타입을 가지는 함수를 생성한다고 보면된다.

위 코드를 예시로 보면

fn generic_function_i32(t: i32) -> i32 {
    t * t
}

fn generic_function_f32(t: f32) -> f32 {
    t * t
}

(함수명이 정확한지는 모름..)

위와 같이 생성한다.

즉, 컴파일 타임은 길어질 수 있으나 런타임 비용은 사라진다고 볼 수 있다.

더 자세한 Monomorphization에 대해서는 아래 사이트를 참조하면 된다.

https://rustc-dev-guide.rust-lang.org/backend/monomorph.html#monomorphization

2. Trait bound

### trait bound

fn main() {
    let dog = Dog;
    let cat = Cat;
    animal_speak(dog);
    animal_speak(cat);
}

fn animal_speak<T: Speak>(animal: T) {
    animal.Speak();
}

trait Speak {
    fn Speak(&self);
}

struct Dog;

impl Speak for Dog {
    fn Speak(&self) {
        println!("멍멍");
    }
}

struct Cat;

impl Speak for Cat {
    fn Speak(&self) {
        println!("야옹");
    }
}

Generic과 마찬가지로 컴파일 타임에 타입이 결정되면서 타입에 맞게 별도의 함수가 생성된다.

3. Iterator

Iterator는 consume되기전까지 아무 동작을 하지 않는다. Lazy Evaluation가 적용이 된다.
가장 손쉽게 실행할 수 있는 방법은 next()를 호출하는 것이다.

    let nums = vec![1,2,3,4,5];

    let iter = nums.iter().map(|x| {
        println!("{}을 2배로 만듦", x);
        x * 2
    });

    println!("아직 아무일도 일어나지 않음 iteraotr의 특징");

    for val in iter {
        println!("최종 값 : {}", val);
    }

위 코드를 실행하면 아래와 같이 결과가 나온다.

#output
아직 아무일도 일어나지 않음 iteraotr의 특징
12배로 만듦
최종 값 : 2
22배로 만듦
최종 값 : 4
32배로 만듦
최종 값 : 6
42배로 만듦
최종 값 : 8
52배로 만듦
최종 값 : 10

Iterator를 다룰 때 크게 2가지로 나뉜다.
1. 설정 단계

  • map(), filter() 같은 메서드로 반복자의 동작을 정의
  • 실제 데이터는 생성되지 않음 (Lazy Evaluation)
  1. 소비 단계
  • collect(), for_each(), sum() 같은 메서드가 호출되면 반복자가 실제 실행됨
  • 이때 next()가 호출되면서 하나씩 값이 생성됨

Lazy Evaluation의 장점

  • 메모리 효율적 → 한 번에 모든 데이터를 생성하지 않고, 필요한 만큼만 메모리에 올림
  • 성능 최적화 → map(), filter() 같은 체이닝 연산을 최적화해서 중간 데이터를 줄임
  • 무한한 데이터 처리 가능 → 무한 스트림도 안전하게 사용 가능

참조 사이트
https://www.reddit.com/r/rust/comments/1gfpty6/media_new_to_rust_and_im_way_off_on_understanding/?rdt=64730

결론

rust는 높은 추상화를 사용하더라도 런타임 코스트가 발생하지 않는다.
그러나 다이나믹 디스패치를 활용할 경우에는 런타임 코스트가 발생한다.
왜 rust의 성능이 좋은지 구체적으로 알 수 있는 시간이었다.
대략적인 개념에 대해 이해했기에 코드를 작성하며 더 깊은 이해를 하도록 노력해야겠다.

0개의 댓글