【问题标题】:Accessing fields of different index in collection inside par_iter_mut访问 par_iter_mut 内集合中不同索引的字段
【发布时间】:2021-06-13 14:25:15
【问题描述】:

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

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 的 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();
       }
    }
}

虽然这会很有效,但它完全弄乱了我的真实代码,并且价值在概念上确实属于 Child。我对 rust 比较陌生,只是问自己是否有任何好的方法可以做到这一点。据我了解,需要一种方法来告诉编译器,我只更改并行迭代器中的距离字段,而值字段保持不变。也许这是一个使用不安全的地方?无论如何,如果您能提示我正确的方向,或者至少确认我的代码确实必须变得更加混乱才能使其工作,我将非常感激:)

【问题讨论】:

  • 听起来像是Cell的典型用例

标签: rust rayon


【解决方案1】:

Rust 极力阻止你做你想做的事:在修改它的同时保留对整个集合的访问。如果您不愿意调整数据的布局以适应借用检查器,您可以使用内部可变性使Child::calculate_distances 采用&amp;self 而不是&amp;mut self。然后你的问题就消失了,因为将多个共享引用分发给self.children 是完全可以的。

理想情况下,您应该使用RefCell,因为您不会从多个线程访问相同的Child。但是 Rust 不允许这样做,因为根据所涉及函数的签名,您可以这样做,这将是一场数据竞赛。声明distances: RefCell&lt;Vec&lt;f64&gt;&gt; 使Child 不再是Sync,删除对Vec&lt;Child&gt;::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::Mutex 替换为parking_lot::Mutex,它更小(只有一个字节的开销,没有分配)、更快,并且不需要unwrap(),因为它不会造成锁中毒。

【讨论】:

  • 显然在 par_iter 中使用互斥锁会导致死锁。这个问题在这里讨论:github.com/rayon-rs/rayon/issues/592我的最终解决方案是将值提取到缓冲区中,因此我不必复制整个子项,这大大减少了我真实代码中不必要的副本的大小。跨度>
  • @Foveng 你是否真的经历过代码中的死锁,或者你只是小心点?锁定这个互斥锁不应该死锁,因为它是完全没有竞争的,并且只是用来向借用检查器证明访问distances 是安全的。问题中的示例与互斥锁的更典型用法有关,其中线程访问受互斥锁保护的共享​​>资源。
  • @Foveng 另外,在链接的问题中,par_iter() 内部有一个par_iter(),其中内部par_iter() 在互斥锁锁定的情况下执行。虽然我知道这种情况可能会出现并且应该被警告,但它不是唯一可能的,甚至不是互斥锁的典型使用。
  • 在我的真实代码中我遇到了死锁,但是我没有用示例代码尝试它。我不能排除我是否忽略了我的真实代码和示例代码之间的一些相关差异。如果示例代码稍后有问题,也许我会尝试。
  • 我尝试了示例代码,没有遇到死锁。这是我使用的代码:fn main() { let mut parent = Parent{ children: Vec::new() }; for i in 0..10000{ parent.children.push(Child{ value: i as f64/2.0, index: i, distances: Mutex::new(vec![0.0;10000]), }) } parent.calculate_distances(); println!("finished"); }
猜你喜欢
  • 1970-01-01
  • 2011-08-13
  • 1970-01-01
  • 2013-11-25
  • 2021-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多