crate 介绍
Rayon是Rust轻量级的数据并行库,可以轻松地将顺序计算转换为并行计算。同时,它还保证了数据竞争的自由。这意味着并行执行不会产生各种疯狂的错误,尽管不是全部,但只要代码可以编译,那执行结果基本是准确且安全的。
示例1 文件夹大小计算(正常)
模拟大量任务
use rayon::prelude::*;
use std::fs;
use std::path::Path;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Instant;
fn main() {
let begin = Instant::now();
let (size, count) = dir_size(r#"C:\Mll"#);
let size = (size as f64) / (1 << 30) as f64;
println!("数量:{count} 大小:{size:.2}G 耗时:{:.2?}", begin.elapsed())
}
pub fn dir_size<P: AsRef<Path>>(path: P) -> (u64, u64) {
// for_each 并行执行会有数据竞态问题它所接受的闭包是 Fn 也就是不可变变量
// 使用原子 U64 来计数, 内部有原子锁防止数据竞态
let size = AtomicU64::new(0);
let count = AtomicU64::new(0);
fs::read_dir(path)
.unwrap()
.par_bridge()
.filter_map(Result::ok)
.for_each(|entry| {
let meta = entry.metadata().unwrap();
// 过滤 link 文件
if meta.is_symlink() {
return;
}
count.fetch_add(1, Ordering::SeqCst);
if meta.is_dir() {
let (s, c) = dir_size(entry.path()); // 递归调用进行深度计算
size.fetch_add(s, Ordering::SeqCst);
count.fetch_add(c, Ordering::SeqCst);
} else {
size.fetch_add(meta.len(), Ordering::SeqCst);
}
});
(size.into_inner(), count.into_inner())
}
开启并行迭代器前
开启并行迭代器后
大量并行计算任务中
rayon提升明显,简单易用只需要在迭代器后面调用par_bridge()即可转换为并行迭代器。
示例2 素数计算(暴力)
暴力素数算法模拟 cpu 密集型任务
use rayon::prelude::*;
use std::time::Instant;
fn main() {
let begin = Instant::now();
let n:usize = 1_000_000;
let res = (2..n)
// .par_bridge()
.filter(|&i| (2..i).find(|f| i % f == 0).is_none())
.count();
println!("范围:2-{n} 数量:{res} 耗时: {:.2?}", begin.elapsed());
}
开启并行迭代器前
开启并行迭代器后
暴力素数计算也能看到非常明显的性能提升,不过快是有代价的,直接榨干了
cpu性能。当然这是一种极端情况,一般情况下并行不一定会消耗大量的cpu性能。
示例3 文件内容检索(疯狂)
模拟大量 io 任务
use rayon::iter::*;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::sync::mpsc;
use std::sync::mpsc::Sender;
use std::time::Instant;
use std::{fs, io, thread};
// 让 build 生成的文件携带这个 标志
const FLAG: &str = "FLAG-123456";
fn main() {
let begin = Instant::now();
let (tx, rx) = mpsc::channel();
let res = thread::spawn(|| rx.into_iter().collect());
file_content_search(".", FLAG.as_bytes(), tx);
let res: Vec<PathBuf> = res.join().unwrap();
println!("找到:{} 耗时:{:.2?}", res.len(), begin.elapsed())
}
fn file_content_search<P: AsRef<Path>>(path: P, search: &[u8], tx: Sender<PathBuf>) {
fs::read_dir(path)
.unwrap()
.par_bridge()
.filter_map(Result::ok)
.for_each(|entry| {
if entry.metadata().unwrap().is_dir() {
return file_content_search(entry.path(), search, tx.clone());
}
let path = entry.path();
if let Ok(true) = bytes_search(&path, search) {
tx.send(path).unwrap()
}
});
}
fn bytes_search<P: AsRef<Path>>(path: P, search: &[u8]) -> io::Result<bool> {
let mut file = fs::File::open(path)?;
let mut buf = [0; 1 << 10];
let mut offset = 0;
loop {
let n = file.read(&mut buf)?;
if n == 0 {
break Ok(false); // EOF
}
unsafe {
for item in buf.get_unchecked(0..n).iter() {
if offset == search.len() {
return Ok(true);
}
if item == search.get_unchecked(offset) {
offset += 1
} else {
offset = 0
}
}
}
}
}
开启并行迭代器前
开启并行迭代器后
开启并行迭代器后扫描 80G 文件内容
我们扫描了项目下所有文件内容并找到 5 个带有
FLAG的文件,这是build生成的。当扫描的目录下有大量的文件时cpu磁盘io都会被瞬间拉满,这就是并行迭代器带来的恐怖性能。这是一种疯狂的行为,不要轻易尝试!
小结
rayon轻量、简单、易用、安全、高效、能轻易榨干电脑性能。- 文中只使用了
par_bridge这个方法rayon还有很多api - 很多库都依赖了
rayon在features中开启(如果有的话)