複数回のメソッドチェーンと1度のループの速度は同じ【Rust】

2 min read読了の目安(約2200字

TLDR

公式の言う通り。
ループとイテレータの速度は同じ。
コンパイル時にループアンローリングするから、最終的なアセンブリはおなじになる。

https://doc.rust-lang.org/book/ch13-04-performance.html

調べたかったこと

ループとイテレータは同じとはいえ、複数のメソッドが連なったチェーンが 1 つではなくて複数個でも同じか調べたかった。

具体的には、Vec<Struct>みたいなヴェクタから、各フィールドの値だけを持つ配列に分離させたかった。
その場合、ループのなかで各配列にプッシュすべきか、元の配列に map メソッドを呼んで目当ての値を抽出したほうが良いのか確認した。

ベンチマーク

ベンチマークを取るため以下のサンプルコードを用意した。
100000 個の要素を持つ元の配列から、いろいろ値に操作を施したうえで 3 つの新しい配列を作った。

use criterion::{black_box, criterion_group, criterion_main, Criterion};

fn iter_methods(n: usize) {
    let v: Vec<(String, usize)> = (0..n).map(|i| (i.to_string(), i + i)).collect();
    let x: Vec<usize> = v.iter().filter(|x| x.1 % 2 == 0).map(|x| x.1 * 2).collect();
    let y: Vec<(String, usize)> = v
        .iter()
        .enumerate()
        .map(|(i, x)| (x.0.clone().chars().rev().collect(), i))
        .collect();
    let z: Vec<String> = v.iter().map(|x| x.1.to_string()).collect();
}

fn iter_loop(n: usize) {
    let v: Vec<(String, usize)> = (0..n).map(|i| (i.to_string(), i + i)).collect();
    let mut x: Vec<usize> = Vec::new();
    let mut y: Vec<(String, usize)> = Vec::new();
    let mut z: Vec<String> = Vec::new();
    for (i, j) in v.iter().enumerate() {
        if j.1 % 2 == 0 {
            x.push(j.1 * 2);
        }
        y.push((j.0.clone().chars().rev().collect(), i));
        z.push(j.1.to_string());
    }
}

pub fn criterion_benchmark(c: &mut Criterion) {
    c.bench_function("method chain", |b| {
        b.iter(|| iter_methods(black_box(100000)))
    });
    c.bench_function("loop", |b| b.iter(|| iter_loop(black_box(100000))));
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

結果

For ループの結果

    Lower bound	Estimate Upper bound
R² 0.0088189 0.0091037 0.0087219
Mean 49.838 ms 50.421 ms 51.098 ms
Std. Dev. 2.0093 ms 3.2410 ms 4.3778 ms
Median 49.157 ms 49.513 ms 50.114 ms
MAD 1.1768 ms 1.5288 ms 2.1470 ms

メソッドチェーンの結果

    Lower bound	Estimate Upper bound
R² 0.0056465 0.0058314 0.0055924
Mean 52.425 ms 52.914 ms 53.473 ms
Std. Dev. 1.8253 ms 2.7077 ms 3.6332 ms
Median 51.998 ms 52.129 ms 52.538 ms
MAD 1.0606 ms 1.4202 ms 2.2163 ms

結論

大差ないので簡潔でイミュータブルにできるメソッドチェーンを使ったほうが良いと思う。
CS 本読んでると公式が低レイヤに言及しても理解できて楽しい。