[Rust] iterator ์„ฑ๋Šฅ ๋ฒค์น˜

iterator๋Š” Rust๊ฐ€ ์ €๋ ดํ•œ ์ถ”์ƒํ™”๋ผ๊ณ  ์ž๋ž‘ํ•˜๋Š” ์ฃผ์š” ์Šฌ๋กœ๊ฑด ์ค‘ ํ•˜๋‚˜๋‹ค. zero cost abstractions๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.
๊ทผ๋ฐ ์ €๋น„์šฉ์œผ๋กœ ์ตœ์ ํ™”๊ฐ€ ๋œ๋‹ค๊ณ ๋Š” ์ฃผ์žฅํ•˜๋Š”๋ฐ, ๊ทธ ๋ฐฉ์‹๊ณผ ์›๋ฆฌ์— ๋Œ€ํ•ด์„œ๋Š” ๋”ฑํžˆ ๋šœ๋ ทํ•œ ์„ค๋ช…์„ ํ•ด์ฃผ์ง€๋Š” ์•Š๋”๋ผ.

๊ทธ๋Ÿฌ๋ฉด ์ง„์งœ๋กœ iterator๊ฐ€ for ๋ฃจํ”„์™€ ๋Œ€๋“ฑํ•œ ์†๋„๋ฅผ ๋‚ด๋Š”๊ฑธ๊นŒ? ๊ถ๊ธˆํ•ด์„œ ์‹คํ—˜๋ถ€ํ„ฐ ํ•ด๋ดค๋‹ค.

pub struct Timer {
    start: std::time::Instant,
}

impl Timer {
    pub fn new() -> Timer {
        Timer {
            start: std::time::Instant::now(),
        }
    }

    pub fn elapsed(&self) -> std::time::Duration {
        self.start.elapsed()
    }

    pub fn elapsed_as_millis(&self) -> u128 {
        self.elapsed().as_millis()
    }

    pub fn elapsed_as_secs(&self) -> u64 {
        self.elapsed().as_secs()
    }
}

fn do_iterator_test(input: Vec<i32>) -> Vec<f64> {
    input.iter().map(|&x| x as f64).collect()
}

fn do_loop_test(input: Vec<i32>) -> Vec<f64> {
    let mut result = Vec::new();
    result.reserve(input.len());
    for i in 0..input.len() {
        result.push(input[i] as f64);
    }
    result
}

fn main() {
    const N: usize = 1_000_000_000;

    let mut loop_test = vec![1; N];
    let mut iter_test = vec![1; N];
    for i in 0..N {
        loop_test[i] = i as i32;
        iter_test[i] = i as i32;
    }

    let timer = Timer::new();
    let sum_1 = do_loop_test(loop_test);
    println!("loop: {}ms", timer.elapsed_as_millis());

    let timer = Timer::new();
    let sum_2 = do_iterator_test(iter_test);
    println!("iter: {}ms", timer.elapsed_as_millis());

    assert_eq!(sum_1, sum_2);
}

๊ทธ๋ƒฅ i32 ๋ฒกํ„ฐ๋ฅผ f64 ๋ฒกํ„ฐ๋กœ ์น˜ํ™˜ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์ฝ”๋“œ๋ฅผ loop ๋ฒ„์ „๊ณผ iterator ๋ฒ„์ „์œผ๋กœ ๊ฐ๊ฐ ์ž‘์„ฑํ•ด๋ดค๋‹ค.
๋‚˜๋Š” ์„ฑ๋Šฅ์ด ๋น„์Šทํ•˜๊ฒŒ ๋‚˜์˜ค๊ฑฐ๋‚˜ ์ผ๋ฐ˜ ๋ฃจํ”„๊ฐ€ ๊ทผ์†Œํ•˜๊ฒŒ ๋น ๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ์˜ˆ์ƒํ–ˆ๋‹ค.

์ฒซ๋ฒˆ์งธ ํ…Œ์ŠคํŠธํ™˜๊ฒฝ์€ Ubuntu 22, x86_64, RAM 32GB์˜€๋‹ค.


ํ•˜์ง€๋งŒ ์˜์™ธ๋กœ iterator์˜ ์ฒ˜๋ฆฌ์†๋„๊ฐ€ ํ›จ์”ฌ ๋นจ๋ž๋‹ค.


release๋กœ ๋นŒ๋“œํ•ด๋„ ๊ทธ๋ ‡๊ณ , ์‹คํ–‰ ์ˆœ์„œ๋ฅผ ๋ฐ˜๋Œ€๋กœ ๋ฐ”๊ฟ”๋„ ๋‹ค๋ฆ„์ด ์—†์—ˆ๋‹ค. ์บ์‹œ์˜ ์˜ํ–ฅ๋„ ์•„๋‹ˆ๋ผ๋Š” ๊ฒƒ์ด๋‹ค.


fn do_loop_test(input: Vec<i32>) -> Vec<f64> {
    let mut result = vec![0_f64; input.len()];
    let mut i = 0;
    while i < input.len() {
        result[i] = input[i] as f64;
        i += 1;
    }
    result
}

vector push๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  ๋ฏธ๋ฆฌ ํ• ๋‹นํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝํ•˜๋‹ˆ ์ข€ ๋Œ€๋“ฑํ•ด์กŒ์œผ๋‚˜, ๊ทธ๋ž˜๋„ ํ•ญ์ƒ iterator๊ฐ€ ๋ฏธ์„ธํ•˜๊ฒŒ ๋นจ๋ž๋‹ค.

๊ทธ๋Ÿผ ๋‹ค๋ฅธ ํ”Œ๋žซํผ์—์„œ๋Š” ์–ด๋–จ๊นŒ?
Mac ํ™˜๊ฒฝ์—์„œ๋„ ํ…Œ์ŠคํŠธํ•ด๋ดค๋‹ค.
์ŠคํŽ™์€ M2 Pro Ventura 13, RAM 16GB์˜€๋‹ค.

Mac์—์„œ์˜ ๋ฒค์น˜๋งˆํฌ ๊ฒฐ๊ณผ๋Š” ์ข€ ๋” ํ˜ผ๋ž€์Šค๋Ÿฌ์› ๋‹ค.
iter ๋ฒ„์ „์„ ๋จผ์ € ์‹คํ–‰ํ•˜๋ฉด iter๊ฐ€ ๋” ๋น ๋ฅด๊ณ , loop ๋ฒ„์ „์„ ๋จผ์ € ์‹คํ–‰ํ•˜๋ฉด loop๊ฐ€ ๋” ๋นจ๋ž๋‹ค. ๊ทผ๋ฐ ์•„๋ฌดํŠผ ์—„์ฒญ ํฐ ์ฐจ์ด๊นŒ์ง€ ๋ฒŒ์–ด์ง€์ง€๋Š” ์•Š์•˜๋‹ค.




LLVM-IR ๊นŒ๋ณด๊ธฐ

์–ด์…ˆ๋ธ”๋ฆฌ ์ˆ˜์ค€๊นŒ์ง€ ๊ฐ€๊ธฐ์—๋Š” ๋„ˆ๋ฌด ๊ณ ํ†ต์Šค๋Ÿฝ๊ณ , llvm์ด ์ž๊ธฐ๋“ค ๋งˆ์Œ๋Œ€๋กœ ์ตœ์ ํ™”๋ฅผ ๊ฐ€ํ•˜๋Š” ๊ฒƒ๋„ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— Rust๊ฐ€ ์ตœ์ ํ™”๋ฅผ ํ•˜๋Š” ๋ถ€๋ถ„์„ ๋ณด๊ณ  ์‹ถ๋‹ค๋ฉด llvm-ir์„ ๊นŒ๋ณด๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ๋ช…ํ™•ํ•œ ๋ฐฉ๋ฒ•์ด๋‹ค.

์šฐ์„  llvm-ir์„ ๋ฝ‘์•„๋ดค๋‹ค.

๊ทธ๋Ÿฌ๋ฉด ์ด๋ ‡๊ฒŒ ์ฃผ์„์ด ์นœ์ ˆํ•˜๊ฒŒ ๋‹ฌ๋ ค์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ณด๋Š” ๊ฒƒ์ด ๊ทธ๋ ‡๊ฒŒ ์–ด๋ ต์ง€๋งŒ์€ ์•Š๋‹ค.

๋ฃจํ”„ ๋ฒ„์ „ ํ•จ์ˆ˜์˜ ๊ฒฝ์šฐ์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ณ ์ „์ ์ธ ํ˜•ํƒœ๋กœ ๋ฃจํ”„ ๊ตฌ๋ฌธ์„ ๊ตฌ์„ฑํ•œ๋‹ค.


i ๋ณ€์ˆ˜๋ฅผ 0์œผ๋กœ ๊น”๊ณ 


๋ฐ˜๋ณต์„ ์œ„ํ•œ length ๊ฐ’์„ ํ• ๋‹นํ•˜๊ณ 


๋บ‘๋บ‘๋Œ๋ฉด์„œ ์ž‘์„ ๋•Œ๋งŒ bb5 ๋ ˆ์ด๋ธ”๋กœ ์ด๋™ํ•œ๋‹ค.


์ข€ ์žฅํ™ฉํ•˜๊ธด ํ•œ๋ฐ, result[i] = input[i] as f64๊ฐ€ ๋ณ€ํ™˜๋œ ๊ฒฐ๊ณผ๋ผ๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.
์˜ค๋ฒ„ํ”Œ๋กœ ์ฒดํฌ๋„ ํ•˜๊ณ , ๋ฐ”์šด๋“œ ์ฒดํฌํ•ด์„œ ํŒจ๋‹‰ ํ•ธ๋“ค๋ง์„ ํ•˜๋Š” ๊ฒƒ๋„ ์žˆ๊ณ , ๋ญ๊ฐ€ ์ธ์ŠคํŠธ๋Ÿญ์…˜์ด ๋งŽ์€๊ฒŒ ๊ทธ๋ƒฅ ๋ด๋„ ๋А๊ปด์งˆ ๊ฒƒ์ด๋‹ค.

๊ทธ์— ๋ฐ˜ํ•ด iterator๋กœ ์ƒ์„ฑ๋œ ์ฝ”๋“œ๋Š”

๋น„๊ต์  ๊ฐ„๋‹จํ•˜๋‹ค.
๋ฐ”์šด๋“œ ์ฒดํฌ๋„ ์—†๊ณ ?, iteration core ํ•จ์ˆ˜๋ฅผ 2๊ฐœ ํ˜ธ์ถœํ•˜๋Š” ์ฝ”๋“œ๋กœ ์ƒ์„ฑ์ด ๋œ๋‹ค.
iterator ์ฝ”๋“œ์˜ ๊ฒฝ์šฐ์—๋Š” ๋ฐ˜๋ณต์˜ ํ˜•ํƒœ๊ฐ€ ์ •ํ˜•ํ™”๋œ ์ฑ„๋กœ ๊ณ ์ •๋˜์–ด์žˆ๋‹ค๋ณด๋‹ˆ ์ข€๋” ๊ฐ„๊ฒฐํ•˜๊ณ  ๊ณผ๊ฒฉํ•œ ํ˜•ํƒœ๋กœ ์ตœ์ ํ™”๊ฐ€ ๊ฐ€๋Šฅํ•œ ๊ฒƒ ๊ฐ™๋‹ค.

๋ฌผ๋ก  ์ธ์ŠคํŠธ๋Ÿญ์…˜์ด ๋งŽ๋”๋ผ๋„ llvm์ด ์•Œ์•„์„œ ์ตœ์ ํ™”๋ฅผ ํ•ด์ค„ ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ์ด ์ •๋„๋กœ ์ฐจ์ด๊ฐ€ ๋‚œ๋‹ค๋ฉด ๊ฒฐ๊ณผ์—๋„ ์œ ์˜๋ฏธํ•œ ์ฐจ์ด๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค. ์ž‘์€ ๋ณ€์ˆ˜ ํ•˜๋‚˜๋งŒ ์žˆ์–ด๋„ ๋ฌด์–ธ๊ฐ€ ๋‚˜์œ ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ๋‹ค๋ฉด ์ตœ์ ํ™” ์ˆ˜๋‹จ์„ ํ•˜๋‚˜ ํฌ๊ธฐํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.




filter์ถ”๊ฐ€ํ•ด๋ณด๊ธฐ

์œ„์—์„œ ํ…Œ์ŠคํŠธํ–ˆ๋˜ ์ฝ”๋“œ๋Š” ํƒ€์ž…๋ณ€ํ™˜ ์ •๋„๋งŒ ํ•˜๋Š” ์•„์ฃผ ๊ฐ„๋‹จํ•œ ์ฝ”๋“œ์˜€๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์ฒด์ด๋‹์„ ์ข€๋” ๋”ํ•ด๋ณด๋ฉด ์–ด๋–จ๊นŒ?

map ํ›„์— filter๋งŒ ํ•œ๋ฒˆ ๋”ํ•ด๋ณด๋Š” ํ˜•ํƒœ๋กœ ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ•ด๋ดค๋‹ค.


์ด ๊ฒฝ์šฐ์—๋Š” iteration์ด ์•ฝ๊ฐ„ ๋А๋ฆฐ ์ •๋„๋กœ ์ตœ์ ํ™”๊ฐ€ ๋˜์—ˆ๋‹ค.
ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์€ Mac M2๋‹ค.

ํ•˜์ง€๋งŒ ์ด ๋ฒ„์ „์˜ iterator ์ฝ”๋“œ์—๋Š” ์‚ฌ์‹ค ์กฐ๊ธˆ ๋น„ํšจ์œจ์ ์ธ ๋ถ€๋ถ„์ด ์žˆ๋‹ค.
filter๋ฅผ ๋จผ์ € ํ•œ ๋’ค์— map์„ ํ•ด๋„ ๋˜๋Š”๋ฐ ๊ทธ ๋ฐ˜๋Œ€๋กœ ํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๊ทธ๋Ÿฌ๋ฉด filter๋กœ ๊ฑธ๋Ÿฌ์งˆ ๋Œ€์ƒ์— ๋Œ€ํ•ด์„œ๋„ ๋ถˆํ•„์š”ํ•œ map ๋ฐ˜๋ณต์„ ๋Œ ๊ฒƒ์ด๋‹ค.

์ด๊ฑธ ๋‹ค์‹œ ์ˆœ์„œ๋งŒ ์กฐ์ •ํ•ด์ค˜๋„

์กฐ๊ธˆ ๋” ๋™๋“ฑํ•œ ์ˆ˜์ค€๊นŒ์ง€ ์ตœ์ ํ™”๊ฐ€ ๋˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค. ๋ฌผ๋ก  ์™”๋‹ค๊ฐ”๋‹ค ํ•˜๊ธด ํ•˜๋Š”๋ฐ, iterator๊ฐ€ ๋น ๋ฅด๊ฒŒ ๋‚˜์˜ฌ ๋•Œ๋„ ์žˆ๋‹ค.

์•„๋ฌดํŠผ ๋ฏธ๋ฌ˜ํ•˜๊ธด ํ•˜์ง€๋งŒ Rust๋Š” map๊ณผ filter๋กœ ๊ตฌ์„ฑ๋œ ๋ณตํ•ฉ iteration์˜ ๊ฒฝ์šฐ์—๋„ ์–ด๋А์ •๋„ for ๋ฃจํ”„์™€ ๋™์ผํ•œ ํ˜•ํƒœ๋กœ ์ตœ์ ํ™”๋ฅผ ํ•ด์ค€๋‹ค. ๊ทธ๋ž˜์„œ for ๋ฒ„์ „์˜ ์ฝ”๋“œ์™€ ์™„๋ฒฝํ•˜๊ฒŒ ๋™์ผํ•œ ๋ฐ”์ด๋„ˆ๋ฆฌ๋ฅผ ๋ฐฉ์ถœํ•  ๋•Œ๋„ ๋งŽ๋‹ค.
์ด๋Ÿฐ๊ฑธ Haskell์—์„œ๋Š” fusion์ด๋ผ๊ณ  ๋ถ€๋ฅด๋”๋ผ.



์ฐธ์กฐ
https://doc.rust-lang.org/book/ch13-04-performance.html
https://ntietz.com/blog/rusts-iterators-optimize-footgun/