rust - 有没有办法让线程不那么激烈地争夺 I/O 资源?
问题描述
我编写了以下并行测试用例,它的运行速度至少比md5sum
二进制慢 2 倍:
// dependencies are md-5 = "0.9.1", rayon = "1.5.0", hex-slice = "0.1.4"
use std::{
convert::TryInto,
env::args_os,
fs::File,
io::{Error, Read},
path::PathBuf,
};
use hex_slice::AsHex;
use md5::{self, digest::Digest};
use rayon::prelude::*;
fn main() {
args_os()
.skip(1)
.collect::<Vec<_>>()
.par_iter()
.for_each(|path| {
println!(
"{:02x} {}",
md5sum(PathBuf::from(&path)).unwrap().plain_hex(false),
path.to_string_lossy()
);
});
}
fn md5sum(path: PathBuf) -> Result<[u8; 16], Error> {
let mut file = File::open(path)?;
let mut ctx = md5::Md5::default();
let mut buf = vec![0u8; 2_097_152];
loop {
let bytes_read = file.read(&mut buf)?;
if bytes_read == 0 {
break;
}
ctx.update(&buf[0..bytes_read]);
}
Ok(ctx.finalize().try_into().unwrap())
}
如果我删除并行性(更改par_iter()
为iter()
),它会比md5sum
二进制文件快一点,所以编译器和 md-5 板条箱肯定不是问题。该算法并非完全受 I/O 限制,因为两个md5sum
进程比一个进程在两个文件上操作要快一点。
测试背景:我在测试之间通过使用 SysInternals RamMap 清空工作集和备用列表来刷新磁盘缓存。其他背景:我在 WSL2 中运行代码,但在 Windows 文件夹中校验和文件。数据位于传统硬盘驱动器上。
我尝试了 ThreadPool,使用 tokio 进行异步读取(与信号量并行+并发以确保没有打开太多文件)和 Rayon(如上所述)。我尝试了从 512 KB 到 10 MB 的缓冲区大小。操作系统或驱动器控制器应该足够智能以尽可能快地读取数据,但它无法正常工作,可能是因为系统想要设置延迟的下限。有没有办法通过并发读取获得不错的性能?
解决方案
不是一个完整的解决方案:通过使用信号量限制 I/O (不是互斥体,因为在 SSD 上我希望允许多次读取),我得到了一些加速。我重新安排了代码以与计算分开进行读取,但这不适合非常大的文件。
读取将是顺序的,但计算可以并行发生:
fn md5sum(io_lock: Arc<Semaphore>, path: PathBuf) -> Result<[u8; 16], Error> {
let buf = {
let _lock = io_lock.access();
let mut file = File::open(path)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
buf
};
Ok(md5::Md5::digest(&buf).try_into().unwrap())
}
很高兴知道是否有办法让操作系统优化读取。
推荐阅读
- sql - 列名有引号
- node.js - 查询参数 - API 休息
- flutter - 对齐 AppBar 按钮中的文本
- html - 在函数内重定向 (React)
- sql - 如何在 Oracle 中排队调用存储过程?
- python - 为什么 python 无法使用 tee 实用程序处理管道?
- c# - 富文本框中的拼写检查在运行中中断
- microsoft-graph-api - 使用 Graph API 读取已删除的群组所有者
- c++ - 我应该在函数中还是全局初始化变量?
- visual-studio-code - 在 Visual Studio Codespaces 环境中总是在新选项卡中打开文件?