为什么我的for循环代码比迭代器慢?

时间:2019-02-02 07:13:09

标签: performance rust

我正在尝试解决leetcode问题MDN documentation。很容易,只需找出糖果种类和糖果一半数量之间的最小值即可。

这是我的解决方案(耗时48ms):

use std::collections::HashSet;

pub fn distribute_candies(candies: Vec<i32>) -> i32 {
    let sister_candies = (candies.len() / 2) as i32;
    let mut kind = 0;
    let mut candies_kinds = HashSet::new();
    for candy in candies.into_iter() {
        if candies_kinds.insert(candy) {
            kind += 1;
            if kind > sister_candies {
                return sister_candies;
            }
        }
    }
    kind
}

但是,我找到了使用迭代器的解决方案:

use std::collections::HashSet;
use std::cmp::min;

pub fn distribute_candies(candies: Vec<i32>) -> i32 {
    min(candies.iter().collect::<HashSet<_>>().len(), candies.len() / 2) as i32
}

花费36毫秒。

我不太明白为什么迭代器解决方案比我的for循环解决方案要快。 Rust在后台执行一些魔术优化吗?

1 个答案:

答案 0 :(得分:4)

主要区别在于迭代器版本internally uses Iterator::size_hint用于确定在收集之前在HashSet中保留多少空间。这样可以避免随着集合的增长而重复地进行重新分配。

您可以使用HashSet::with_capacity代替HashSet::new来完成相同的操作:

let mut candies_kinds = HashSet::with_capacity(candies.len());

在我的基准测试中,此更改使您的代码比迭代器快得多。但是,如果我简化您的代码以消除早期的紧急救援优化,则其运行时间几乎与迭代器版本相同。

pub fn distribute_candies(candies: &[i32]) -> i32 {
    let sister_candies = (candies.len() / 2) as i32;
    let mut candies_kinds = HashSet::with_capacity(candies.len());
    for candy in candies.into_iter() {
        candies_kinds.insert(candy);
    }
    sister_candies.min(candies_kinds.len() as i32)
}

时间:

test tests::bench_iter                          ... bench:     262,315 ns/iter (+/- 23,704)
test tests::bench_loop                          ... bench:     307,697 ns/iter (+/- 16,119)
test tests::bench_loop_with_capacity            ... bench:     112,194 ns/iter (+/- 18,295)
test tests::bench_loop_with_capacity_no_bailout ... bench:     259,961 ns/iter (+/- 17,712)

这向我表明HashSet的预分配是主要差异。您的额外优化也被证明是非常有效的-至少对于我碰巧选择的数据集而言。