是`iter()。map()。sum()`和`iter()。fold()`一样快?

时间:2016-11-05 17:46:08

标签: rust

编译器是否为iter().map().sum()iter().fold()生成相同的代码?最后他们实现了相同的目标,但第一个代码将迭代两次,一次针对map,一次针对sum

这是一个例子。 total中哪个版本会更快?

pub fn square(s: u32) -> u64 {
    match s {
        s @ 1...64 => 2u64.pow(s - 1),
        _ => panic!("Square must be between 1 and 64")
    }
}

pub fn total() -> u64 {
    // A fold
    (0..64).fold(0u64, |r, s| r + square(s + 1))
    // or a map
    (1..64).map(square).sum()
}

查看装配或基准测试的好工具是什么?

2 个答案:

答案 0 :(得分:19)

要让他们生成相同的代码,他们首先必须做同样的事情。你的两个例子不是:

fn total_fold() -> u64 {
    (0..64).fold(0u64, |r, s| r + square(s + 1))
}

fn total_map() -> u64 {
    (1..64).map(square).sum()
}

fn main() {
    println!("{}", total_fold());
    println!("{}", total_map());
}
18446744073709551615
9223372036854775807

让我们假设你的意思

fn total_fold() -> u64 {
    (1..64).fold(0u64, |r, s| r + square(s + 1))
}

fn total_map() -> u64 {
    (1..64).map(|i| square(i + 1)).sum()
}

有几种途径需要检查:

  1. 生成的LLVM IR
  2. 生成的程序集
  3. 基准
  4. IR和汇编的最简单的来源是游乐场之一(officialalternate)。这两个都有按钮来查看组件或IR。您还可以将--emit=llvm-ir--emit=asm传递给编译器以生成这些文件。

    确保在发布模式下生成装配或IR。属性#[inline(never)]通常可用于保持函数分离,以便在输出中更容易找到它们。

    基准测试为documented in The Rust Programming Language,因此无需重复所有有价值的信息。

    Rust 1.14 之前,这些不会产生完全相同的程序集。在我担心之前,我等待基准测试/分析数据,看看是否会对性能产生任何有意义的影响。

    Rust 1.14 开始,他们会生成相同的程序集!这是我爱Rust的原因之一。您可以编写清晰且惯用的代码和smart people come along and make it equally as fast

      

    但第一段代码会迭代两次,一次针对map,一次针对sum

    这是不正确的,我很想知道消息来源告诉你什么,所以我们可以在那时纠正它并防止将来的误解。 An iterator以拉动为基础;一次处理一个元素。核心方法是next,它产生一个单独的值,运行足够的计算来产生该值。

答案 1 :(得分:5)

首先,让我们修复这些示例以实际返回相同的结果:

pub fn total_fold_iter() -> u64 {
    (1..65).fold(0u64, |r, s| r + square(s))
}

pub fn total_map_iter() -> u64 {
    (1..65).map(square).sum()
}

现在,让我们从fold开始开发它们。 fold只是一个循环和累加器,大致等同于:

pub fn total_fold_explicit() -> u64 {
    let mut total = 0;
    for i in 1..65 {
        total = total + square(i);
    }
    total
}

然后,让我们选择mapsum,先解开sum大致相当于:

pub fn total_map_partial_iter() -> u64 {
    let mut total = 0;
    for i in (1..65).map(square) {
        total += i;
    }
    total
}

这只是一个简单的累加器!现在,让我们打开map图层(仅应用一个函数),获得大致等同于:

的内容
pub fn total_map_explicit() -> u64 {
    let mut total = 0;
    for i in 1..65 {
        let s = square(i);
        total += s;
    }
    total
}

正如您所看到的,它们都非常相似:它们以相同的顺序应用相同的操作并具有相同的整体复杂性。

哪个更快?我不知道。微观基准测试可能只能告诉你真相的一半:只是因为微基准测试中的某些东西更快并不意味着它在其他代码中更快。

然而, 所说的是,它们都具有相同的复杂性,因此应该表现得相似,即在彼此的因素内。

我个人会选择map + sum,因为它更明确地表达了意图,而foldIterator方法的“厨房接收器”因此信息量较少。