rust 并行迭代器 rayon 的使用

660 阅读3分钟

crate 介绍

RayonRust 轻量级的数据并行库,可以轻松地将顺序计算转换为并行计算。同时,它还保证了数据竞争的自由。这意味着并行执行不会产生各种疯狂的错误,尽管不是全部,但只要代码可以编译,那执行结果基本是准确且安全的。

示例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())
}

开启并行迭代器前

image.png

开启并行迭代器后

image.png

大量并行计算任务中 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());
}

开启并行迭代器前

image.png

开启并行迭代器后

image.png

暴力素数计算也能看到非常明显的性能提升,不过快是有代价的,直接榨干了 cpu 性能。当然这是一种极端情况,一般情况下并行不一定会消耗大量的 cpu 性能。

image.png

示例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
                }
            }
        }
    }
}

开启并行迭代器前

94e8e7140f39beb6c9cd881b1f6335cc.png

开启并行迭代器后

d183eeab8ec9f6d163646a048e5a8db4.png

开启并行迭代器后扫描 80G 文件内容

image.png

我们扫描了项目下所有文件内容并找到 5 个带有 FLAG 的文件,这是 build 生成的。当扫描的目录下有大量的文件时 cpu 磁盘io 都会被瞬间拉满,这就是并行迭代器带来的恐怖性能。这是一种疯狂的行为,不要轻易尝试!

小结

  • rayon 轻量、简单、易用、安全、高效、能轻易榨干电脑性能。
  • 文中只使用了 par_bridge 这个方法 rayon 还有很多 api
  • 很多库都依赖了 rayonfeatures 中开启(如果有的话)

相关