【问题标题】:Most efficient way to fill a vector from back to front从后到前填充矢量的最有效方法
【发布时间】:2016-09-06 06:31:54
【问题描述】:

我正在尝试用一系列值填充向量。为了计算第一个值,我需要计算第二个值,这取决于第三个值等等。

let mut bxs = Vec::with_capacity(n);

for x in info {
    let b = match bxs.last() {
        Some(bx) => union(&bx, &x.bbox),
        None => x.bbox.clone(),
    };
    bxs.push(b);
}
bxs.reverse();

目前我只是使用v.push(x) 从前到后填充矢量,然后使用v.reverse() 反转矢量。有没有办法一次性做到这一点?

【问题讨论】:

  • 这听起来很适合递归。
  • unsafe 方式可能会更快,但我不确定是否值得。
  • FWIW,我会完全按照您的描述进行操作,按顺序推送然后反转。由于您肯定会进行一些分析来测试一次性解决方案是否更有效,您能否向我们展示表明两次解决方案效率不高的分析结果?
  • @Shepmaster 我认为你是对的,两次传球更好。代码的简单性和性能之间的权衡不值一提。
  • 虽然我不确定为什么两次传球会比一次传球更有效率。我能想到的唯一原因是方向会混淆预取器,但英特尔 CPU 可以检测正向或反向的内存访问流 (stackoverflow.com/questions/1950878/…)。此 cofr 是光线追踪器的一部分,其中性能非常重要,对于大图像,代码段可能会运行数百万次。

标签: vector iterator rust


【解决方案1】:

有没有办法一次性做到这一点?

如果你不介意调整向量,那还是比较容易的。

struct RevVec<T> {
    data: Vec<T>,
}

impl<T> RevVec<T> {
    fn push_front(&mut self, t: T) { self.data.push(t); }
}

impl<T> Index<usize> for RevVec<T> {
    type Output = T;
    fn index(&self, index: usize) -> &T {
        &self.data[self.len() - index - 1]
    }
}

impl<T> IndexMut<usize> for RevVec<T> {
    fn index_mut(&mut self, index: usize) -> &mut T {
        let len = self.len();
        &mut self.data[len - index - 1]
    }
}

【讨论】:

  • 您是否实际上将数据按倒序保留,但使访问从后到前工作?聪明的。当您公开这些情况时,您必须注意任何其他情况(迭代器等)。
  • @Shepmaster:是的,就是这个想法,实际上这意味着反转所有访问。
【解决方案2】:

使用unsafe 的解决方案如下。不安全版本的速度比使用 reverse() 的安全版本快 2 倍多一点。这个想法是使用Vec::with_capacity(usize)分配向量,然后使用ptr::write(dst: *mut T, src: T)将元素从后往前写入向量。 offset(self, count: isize) -&gt; *const T 用于计算向量的偏移量。

extern crate time;
use std::fmt::Debug;
use std::ptr;
use time::PreciseTime;

fn scanl<T, F>(u : &Vec<T>, f : F) -> Vec<T>
    where T : Clone,
          F : Fn(&T, &T) -> T {
    let mut v = Vec::with_capacity(u.len());

    for x in u.iter().rev() {
        let b = match v.last() {
            None => (*x).clone(),
            Some(y) => f(x, &y),
        };
        v.push(b);
    }
    v.reverse();
    return v;
}

fn unsafe_scanl<T, F>(u : &Vec<T> , f : F) -> Vec<T>
    where T : Clone + Debug,
          F : Fn(&T, &T) -> T {
    unsafe {
        let mut v : Vec<T> = Vec::with_capacity(u.len());

        let cap = v.capacity();
        let p = v.as_mut_ptr();

        match u.last() {
            None => return v,
            Some(x) => ptr::write(p.offset((u.len()-1) as isize), x.clone()),
        };
        for i in (0..u.len()-1).rev() {
            ptr::write(p.offset(i as isize), f(v.get_unchecked(i+1), u.get_unchecked(i)));
        }
        Vec::set_len(&mut v, cap);
        return v;
    }
}

pub fn bench_scanl() {
    let lo : u64 = 0;
    let hi : u64 = 1000000;
    let v : Vec<u64> = (lo..hi).collect();

    let start = PreciseTime::now();
    let u = scanl(&v, |x, y| x + y);
    let end= PreciseTime::now();
    println!("{:?}\n in {}", u.len(), start.to(end));

    let start2 = PreciseTime::now();
    let u = unsafe_scanl(&v, |x, y| x + y);
    let end2 = PreciseTime::now();
    println!("2){:?}\n in {}", u.len(), start2.to(end2));
}

【讨论】:

  • 你probably don't need to use pointer offsets。此外,您的代码具有非惯用的 Rust,例如对 Vec::set_len 的 UFCS 调用、显式类型、&amp;Vec 而不是 &amp;[T]、比需要更宽的 unsafe 块、return 语句、: 之前的空格类型等。您可能希望在某些时候获得惯用的评论。
  • 欢迎任何对该语言有更多经验的人来改进解决方案。
  • @JustinRaymond 你错过了几个assert! / assert_eq! 的电话。 unsafe 代码在我看来并不安全。
猜你喜欢
  • 2022-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-19
  • 1970-01-01
  • 2017-05-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多