前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Rayon魔法:使Rust并行编程变得轻而易举

Rayon魔法:使Rust并行编程变得轻而易举

作者头像
newbmiao
发布2024-01-17 16:08:13
1810
发布2024-01-17 16:08:13
举报
文章被收录于专栏:学点Rust学点Rust

Rayon库是一个数据并行化(data-parallelism)的 Rust库。在并行编程里是一个很有趣的存在, 且非常的容易上手。它可以很轻松地将同步计算流程转化为并行计算。而且基本能保证编译通过就不会有data race

文章目录

  • 同步转并行
  • 背后的魔法
  • join
  • par_bridge

同步转并行

假设有个如下的求和的同步代码

代码语言:javascript
复制
fn main() {
   let sum: i32 = (0..100)
        .into_iter()
        .map(|i| {
            // Simulate some computation
            sleep(Duration::from_nanos(1));
            i
        })
        .sum();
    assert_eq!(sum, 4950);
}

想要转成并行,只需要into_iter变成into_par_iter

Rayon会将同步的遍历转成并行的遍历,而且保证返回的顺序是一致的,瞬间并行是不是!

代码语言:javascript
复制
use rayon::prelude::*;
fn main() {
    let sum: i32 = (0..100)
        .into_par_iter() // 这里
        .map(|i| {
            // Simulate some computation
            sleep(Duration::from_nanos(1));
            i
        })
        .sum();
    assert_eq!(sum, 4950);
}

divan在 10 核的 M1 pro 上测试结果如下,一行改变让代码速度提升了不少。

Benchmark

Fastest

Slowest

Median

Mean

Samples

Iterations

iter

549.2 µs

1.244 ms

687.4 µs

738.5 µs

100

100

par_iter

195 µs

488.1 µs

315.1 µs

321.9 µs

100

100

背后的魔法

这个并行遍历是怎么处理的呢?

Rayon利用一个可伸缩线程池来执行并行任务,默认情况下,线程池的大小与系统的逻辑核心数量相匹配。

在进行并行任务时,Rayon将当前任务拆分成多个子任务(依据线程池大小),并尽可能地将它们分配给空闲的线程以执行,每个线程有自己的本地任务队列。

如果当前有空闲线程,但已分配的任务仍在等待其线程完成当前任务,空闲线程将尝试执行work stealing,从其他线程任务队列中中窃取一些任务来执行,以确保最大程度地利用 CPU 资源。

最终,将并行任务的结果进行两两合并,将线程结果全部汇总以完成整个并行计算过程。

这里任务拆分和work stealing就是将并行任务分而治之的精髓。

join

其底层很多使用了join, 将两个任务并行执行,并等待任务结果一起返回:

代码语言:javascript
复制
use rayon::prelude::*;

fn main() {
    let v1 = vec![1, 2, 3, 4, 5];
    let v2 = vec![6, 7, 8, 9, 10];

    let (sum1, sum2) = rayon::join(
        || v1.par_iter().sum::<i32>(),
        || v2.par_iter().sum::<i32>()
    );

    println!("sum1: {}, sum2: {}", sum1, sum2);
}

par_bridge

常规能很容易并行化拆分的par_iter就可以了,但是如果遇到不容易并行化的(有阻塞等待等),如channel或者文件、网络 IO 的操作, 则可以用par_bridge

性能会有些损耗,因为其执行的方式是每次获取下一个可遍历的内容,分发到线程池内可用线程上执行,同时也不保证结果返回的顺序。

代码语言:javascript
复制
use rayon::iter::ParallelBridge;
use rayon::prelude::ParallelIterator;
use std::sync::mpsc::channel;

fn main() {
    let rx = {
        let (tx, rx) = channel();

        (1..=3).into_iter().for_each(|i| {
            let _ = tx.send(i);
        });

        rx
    };

    let mut output: Vec<i32> = rx.into_iter().par_bridge().collect();
    output.sort_unstable(); // 重新保证顺序

    assert_eq!(&*output, &[1, 2, 3]);
}

总之,对于串行化遍历任务,一般都可以用Rayon转化为并行处理,当然也要看有没有转化的必要,常规简单遍历自然是不需要并行化的,毕竟线程和任务并行调度也是有开销的。

想了解更多,推荐看看Rayon: data parallelism in Rust[1]

参考资料

[1]

Rayon: data parallelism in Rust: https://smallcultfollowing.com/babysteps/blog/2015/12/18/rayon-data-parallelism-in-rust/

如果有用,点个 在看,让更多人看到

外链不能跳转

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-01-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 菜鸟Miao 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 同步转并行
  • 背后的魔法
  • join
  • par_bridge
相关产品与服务
GPU 云服务器
GPU 云服务器(Cloud GPU Service,GPU)是提供 GPU 算力的弹性计算服务,具有超强的并行计算能力,作为 IaaS 层的尖兵利器,服务于深度学习训练、科学计算、图形图像处理、视频编解码等场景。腾讯云随时提供触手可得的算力,有效缓解您的计算压力,提升业务效率与竞争力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档