rust - 访问 par_iter_mut 内集合中不同索引的字段

标签 rust rayon

以下示例说明了我正在尝试做的事情:

use rayon::prelude::*;

struct Parent {
    children: Vec<Child>,
}
struct Child {
    value: f64,
    index: usize,
    //will keep the distances to other children in the children vactor of parent
    distances: Vec<f64>,
}

impl Parent {
    fn calculate_distances(&mut self) {
        self.children
            .par_iter_mut()
            .for_each(|x| x.calculate_distances(&self.children));
    }
}

impl Child {
    fn calculate_distances(&mut self, children: &[Child]) {
        children
            .iter()
            .enumerate()
            .for_each(|(i, x)| self.distances[i] = (self.value - x.value).abs());
    }
}
以上不会编译。问题是,我无法在第一个 for_each 的关闭中访问 &self.children。我确实明白,为什么借用检查器不允许这样做,所以我的问题是,是否有办法让它只做很少的改动。到目前为止我找到的解决方案并不令人满意。一种解决方案是在 Parent::calculate 距离的开头克隆 child 并在闭包内使用它(这会添加不必要的克隆)。另一种解决方案是像这样提取 Child 的 value 字段:
use rayon::prelude::*;
struct Parent {
    children: Vec<Child>,
    values: Vec<f64>
}
struct Child {
    index: usize,
    //will keep the distances to other children in the children vactor of parent
    distances: Vec<f64>,
}

impl Parent {
    fn calculate_distances(&mut self) {
      let values = &self.values;
        self.children
            .par_iter_mut()
            .for_each(|x| x.calculate_distances(values));
    }
}

impl Child {
    fn calculate_distances(&mut self, values: &[f64]) {
       for i in 0..values.len(){
           self.distances[i]= (values[self.index]-values[i]).abs();
       }
    }
}
虽然这会很有效,但它完全弄乱了我的真实代码,并且值(value)在概念上真的属于 Child。我对 rust 比较陌生,只是问自己是否有任何好的方法可以做到这一点。据我了解,需要一种方法来告诉编译器,我只更改并行迭代器中的距离字段,而值字段保持不变。也许这是一个使用不安全的地方?无论如何,如果你能提示我正确的方向,我将非常感激,或者至少确认我的代码真的必须变得更加困惑才能使其工作:)

最佳答案

Rust 努力阻止你做你想做的事:在修改它的同时保留对整个集合的访问。如果您不愿意调整数据的布局以适应借用检查器,您可以使用内部可变性来制作 Child::calculate_distances&self而不是 &mut self .然后你的问题就消失了,因为将多个共享引用分发到 self.children 是非常好的。 .
理想情况下,您会使用 RefCell 因为你没有访问相同的Child从多个线程。但是 Rust 不允许这样做,因为根据所涉及函数的签名,您可以这样做,这将是一场数据竞赛。声明 distances: RefCell<Vec<f64>>使Child不再 Sync ,删除对 Vec<Child>::par_iter() 的访问权限.
你可以做的是使用 Mutex .虽然一开始感觉很浪费,但请记住,每次调用 Child::calculate_distances()收到不同的Child ,因此互斥体将始终没有竞争,因此锁定成本低(不涉及系统调用)。每Child::calculate_distances() 只锁定一次,而不是每次访问数组。代码如下所示(playground):

use rayon::prelude::*;
use std::sync::Mutex;

struct Parent {
    children: Vec<Child>,
}
struct Child {
    value: f64,
    index: usize,
    //will keep the distances to other children in the children vactor of parent
    distances: Mutex<Vec<f64>>,
}

impl Parent {
    fn calculate_distances(&mut self) {
        self.children
            .par_iter()
            .for_each(|x| x.calculate_distances(&self.children));
    }
}

impl Child {
    fn calculate_distances(&self, children: &[Child]) {
        let mut distances = self.distances.lock().unwrap();
        children
            .iter()
            .enumerate()
            .for_each(|(i, x)| distances[i] = (self.value - x.value).abs());
    }
}
您也可以尝试替换std::sync::Mutexparking_lot::Mutex更小(只有一个字节的开销,没有分配),更快,并且不需要 unwrap()因为它不会造成锁中毒。

关于rust - 访问 par_iter_mut 内集合中不同索引的字段,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66652923/

相关文章:

generics - 既然 num::Zero 和 One 已被弃用,应该如何提供 1 或 0?

rust - 如何实现 Rust 的 Copy 特性?

file-io - rayon::str::Lines <'_' > 不是迭代器

parallel-processing - 程序仍然使用 par_iter 和 par_extend 在一个线程上运行

multithreading - 在 Rust 中使用 `Result<T, Box<dyn Error>>` 技巧,但跨多个线程

multithreading - 如何更改 Rayon 使用的线程数?

reference - 如何为对结构的引用实现 Add 特性?

recursion - 有没有办法优化这段代码,使其不会溢出堆栈?

error-handling - 如何返回包含结果迭代器的每个错误的结果,而不仅仅是第一个错误?

rust - 调用iter()。sum(): the trait bound `f64: std::iter::Sum<&std::vec::Vec<f64>>` is not satisfied时出错