繁体   English   中英

使用人造丝并行化 rust 中的嵌套循环

[英]Parallelizing nested loops in rust with rayon

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

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 as mutable 此处发生第二次可变借用

但它没有用。 我的问题与帖子中的问题有点不同,因为我在一次迭代中修改了两个元素并尝试将它们都借为可变的,而 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 新手,不胜感激!

您尝试做的事情并不像您想象的那么简单: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] 处修改 object。 所以让我们根据该模式分割切片

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

希望这有帮助; ;)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM