切片数组或使用Iterator :: skip更有效吗?

时间:2015-09-21 08:34:15

标签: arrays vector rust monads slice

我想为切片[0+k .. n]中的每个元素调用一个函数,其中k是偏移量,n是向量中元素的数量。重要的是,我想要原始切片中元素的索引。

我找到了两种方法:

  1. 使用enumerateskip开头项目

    vec.iter().enumerate().skip(k).map(|(i, v)| (f(v), i)).min()
    
  2. 取一个子切片,并从`enumerate

    中将偏移量添加到索引中
    vec[k..].iter().enumerate().map(|(i, v)| (f(v), i + k)).min()
    
  3. 在这两种情况下,vec都是字符串向量,f返回字符串中的特定字符(v.chars().nth(offset))。哪种解决方案最有效?

1 个答案:

答案 0 :(得分:4)

我们以此代码为例。它与你的例子类似,但有点简单:

fn main() {
    let items = ["a", "bb", "ccc", "dddd", "eeeee"];
    let k = 3;

    let one = items.iter().enumerate().skip(k).map(|(i, v)| (v.len(), i));
    let two = items[k..].iter().enumerate().map(|(i, v)| (v.len(), i + k));

    // Sanity check that the results are the same
    let items1: Vec<_> = one.collect();
    let items2: Vec<_> = two.collect();

    println!("{}", items1 == items2);
}

哪个性能更高是一个棘手的话题。 Rust和LLVM有很好的优化,可以使代码非常快。

纯粹基于我的直觉,我可能会使用第一个,如果我知道我将跳过“一些”项目,第二个如果我没有'知道有多少或者是否有很多。

在第一种情况下,您在概念上必须遍历要跳过的所有项目。优化程序可能会降低此值,但由于与enumeratemap的交互很复杂,所以如果不检查程序集,我就不会指望它。

第二个(items[k..])使用子操作,这将是一个O(1)操作,因为它只是索引到一块内存。然后你做了添加,这也很简单。

然而,唯一真正的考验是进行一些分析。我们将创建一个大型输入数组并从零开始:

fn main() {
    let items = ["a", "bb", "ccc", "dddd", "eeeee"];
    let items: Vec<_> = items.iter().cycle().take(10_000_000).collect();

    let k = 371_223;

    // let one = items.iter().enumerate().skip(k).map(|(i, v)| (v.len(), i));
    let two = items[k..].iter().enumerate().map(|(i, v)| (v.len(), i + k));

    // let items1: Vec<_> = one.collect();
    let items2: Vec<_> = two.collect();

    // println!("{}", items1.len());
    println!("{}", items2.len());
}

运行该代码,使用优化编译,具有以下平均10次运行的时间:

  1. 153.6ms
  2. 160.1ms
  3. 所以,与我的直觉所说的相反,第一个版本更快。完全可能我的基准测试不正确,但这就是为什么你应该对你的真实代码进行基准测试。

    另外,请注意,非常快。每个项目大约15或16 纳秒。朋友之间的纳秒是多少?