3

我正在尝试并行化 Rust 中的简单嵌套 for 循环,rayon但无法:

fn repulsion_force(object: &mut Vec<Node>) {
    let v0 = 500.0;
    let dx = 0.1;

    for i in 0..object.len() {
        for j in i + 1..object.len() {
            let dir = object[j].position - object[i].position;
            let l = dir.length();
            let mi = object[i].mass;
            let mj = object[j].mass;

            let c = (dx / l).powi(13);
            let v = dir.normalize() * 3.0 * (v0 / dx) * c;

            object[i].current_acceleration -= v / mi;
            object[j].current_acceleration += v / mj;
        }
    }
}

试图关注这篇文章并创建了这个:

use rayon::prelude::*;

object.par_iter_mut()
    .enumerate()
    .zip(object.par_iter_mut().enumerate())
    .for_each(|((i, a), (j, b))| {
    if j > i {
        // code here
    } 
});

一次不能*object多次借用可变的借用第二个可变借用发生在这里

但它没有用。我的问题与帖子中的问题有点不同,因为我在一次迭代中修改了两个元素并试图将它们都借用为 Rust 不喜欢的可变元素,而我不喜欢在不需要时进行双倍计算的想法。

另一种尝试是迭代Range

use rayon::prelude::*;

let length = object.len();

(0..length).par_bridge().for_each(|i| {
    (i+1..length).for_each(|j| {
        let dir = object[j].position - object[i].position;
        let l = dir.length();
        let mi = object[i].mass;
        let mj = object[j].mass;

        let c = (dx / l).powi(13);
        let v = dir.normalize() * 3.0 * (v0 / dx) * c;

        object[i].current_acceleration -= v / mi;
        object[j].current_acceleration += v / mj;
    });

不能借用object可变的,因为它是Fn闭包中的捕获变量

老实说,这个我根本不明白,E0596也没有多大帮助——我object的是&mut. Rust 新手,希望有任何帮助!

4

1 回答 1

2

你想要做的并不像你想象的那么微不足道:D 但让我们试一试吧!

首先,让我们做一个可重现的最小示例,这是在 stackoverflow 上提问的常用方法。正如您可以想象的那样,我们不知道您的代码应该做什么。我们也没有时间尝试弄清楚。
我们想要一个简单的代码片段,它可以完整地描述问题,复制粘贴它,运行它并得出一个解决方案。

所以这是我的最小示例:

#[derive(Debug)]
pub struct Node {
    value: i32,
    other_value: i32,
}

fn repulsion_force(object: &mut [Node]) {
    for i in 0..object.len() {
        for j in i + 1..object.len() {
            let mi = 2 * object[i].value;
            let mj = mi + object[j].value;
            object[i].other_value -= mi;
            object[j].other_value += mj;
        }
    }
}

首先,我创建了一个简单的节点类型。其次,我简化了操作。请注意,我不是传递一个向量,而是传递一个可变切片。这种形式保留了更大的灵活性,以防我需要将切片形式传递给例如数组。由于您没有使用 push(),因此无需引用向量。

所以接下来让我们重新制定并行计算的问题。首先考虑循环的结构和访问模式。您正在迭代切片中的所有元素,但对于每次 i 迭代,您只修改 [i] 和 [j > i] 处的对象。所以让我们根据该模式分割切片

fn repulsion_force(object: &mut [Node]) {
    for i in 0..object.len() {
        let (left, right) = object.split_at_mut(i + 1);
        let mut node_i = &mut left[i];
        right.iter_mut().for_each(|node_j| {
            let mi = 2 * node_i.value;
            let mj = mi + node_j.value;
            node_i.other_value -= mi;
            node_j.other_value += mj;
        });
    }
}

通过分割切片,我们得到两个切片。左切片包含 [i],右切片包含 [j > i]。接下来我们依赖迭代器而不是索引进行迭代。

下一步是使内部循环并行。但是,内部循环会在每次迭代时修改 node_i。这意味着多个线程可能同时尝试写入 node_i,从而导致数据竞争。因此编译器不允许这样做。解决方案是包含一个同步机制。对于一般类型,这可能是互斥体。但是由于您使用的是标准数学运算,因此我选择了原子运算,因为它们通常更快。所以我们将 Node 类型和内部循环修改为

#[derive(Debug)]
pub struct Node {
    value: i32,
    other_value: AtomicI32,
}

fn repulsion_force(object: &mut [Node]) {
    for i in 0..object.len() {
        let (left, right) = object.split_at_mut(i + 1);
        let mut node_i = &mut left[i];
        right.iter_mut().par_bridge().for_each(|node_j| {
            let mi = 2 * node_i.value;
            let mj = mi + node_j.value;
            node_i.other_value.fetch_sub(mi, Relaxed);
            node_j.other_value.fetch_add(mj, Relaxed);
        });
    }
}

您可以使用代码段测试代码

fn main() {
    // some arbitrary object vector
    let mut object: Vec<Node> = (0..100).map(|k| Node { value: k, other_value: AtomicI32::new(k) }).collect();
    repulsion_force(&mut object);
    println!("{:?}", object);
}

希望这有帮助!;)

于 2022-01-20T17:24:33.660 回答