「Rayon → 并行流处理」开始

1,131 阅读2分钟

「这是我参与11月更文挑战的第 22 天,活动详情查看:2021最后一次更文挑战


大多数 Rust 程序员都听说过Rayon,它是一个让程序引入并行性变得几乎神奇很可能超神的库。在这篇文章中,我们将研究如何将Rayon应用于基本的流处理。

例子之前

要运行本文中的例子,请创建一个目录,在其中运行 cargo init --bin,并编辑生成的Cargo.toml,然后填入以下依赖项:

[dependencies]rayon = "1.3.1"
serde_json = "1.0.57"

例子中的代码只是进入了 src/main.rs。你可以用 cargo run --release 来运行它,或者用cargo build --release 来构建它,然后以 target/release/<directory-name> 的形式运行它。在检查Rust中任何东西的性能时,请使用 发布模式,因为你在调试模式下得到的数字是完全没有意义的。

其实上面都是废话,如果你写过 Rust 直接跳过上述内容。前面是写给新人的。。。

基础知识

Rayon是一个很容易的并行处理库。你不需要管理 channels、events、futures,甚至不需要知道线程池,你只需要告诉Rayon你想并行执行什么,Rayon就可以做到。借用github页面上的例子:这里有一个顺序程序,对一个slice的元素的平方进行求和。

fn sum_of_squares(input: &[u64]) -> u64 {
    input.iter()
         .map(|&i| i * i)
         .sum()
}

为了能使它并行,你只需要把 iter → par_iter:

use rayon::prelude::*;

fn sum_of_squares(input: &[u64]) -> u64 {
    input.par_iter()
         .map(|&i| i * i)
         .sum()
}

Rayon兑现了它的承诺,发布了一个并行版本,运行速度快了1.5倍,在我的机器上使用10亿个整数的vector进行测试。考虑到这个例子中单独的map和reduce操作只需要一条CPU指令,这一点要注意一下。

work steal

Rayon的实现使用了一种叫做 "work steal" 的技术,以确保任务在线程之间的有效分配。

work steal 背后的想法是,每个线程维护一个本地任务队列,该队列中的任务由运行在该线程的代码提交。默认情况下,线程运行本地任务,只有当它用完这些任务时,它才会去 "偷" 其他线程队列中的任务。如果所有的线程都同样繁忙,每个线程只需服务于自己的任务,最大限度地提高数据定位,减少上下文切换的次数。