将多个文件读入一个文件的最佳方式是什么



这里有一个比较将多个文件读取为一个文件的两个函数的基准。一个使用CCD_ 1,另一个使用CCD_。我最初的动机是在过程结束时使缓冲区的capacity等于lenread_to_end没有出现这种情况,这是非常不令人满意的。

然而,对于read,这是可行的。read_files_into_file2assert_eq!(buf.capacity(), buf.len());(使用read(不会死机。

use criterion::{criterion_group, criterion_main, Criterion};
use std::io::Read;
use std::io::Write;
use std::{
fs,
io::{self, Seek},
};
fn criterion_benchmark(c: &mut Criterion) {
let mut files = get_test_files().unwrap();
let mut file = fs::File::create("output").unwrap();
c.bench_function("1", |b| {
b.iter(|| {
read_files_into_file1(&mut files, &mut file).unwrap();
})
});
c.bench_function("2", |b| {
b.iter(|| {
read_files_into_file2(&mut files, &mut file).unwrap();
});
});
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
/// Goes back to the start so that the files can be read again from the start.
fn reset(files: &mut Vec<fs::File>, file: &mut fs::File) {
file.seek(io::SeekFrom::Start(0)).unwrap();
for file in files {
file.seek(io::SeekFrom::Start(0)).unwrap();
}
}
pub fn read_files_into_file1(files: &mut Vec<fs::File>, file: &mut fs::File) -> io::Result<()> {
reset(files, file);
let total_len = files
.iter()
.map(|file| file.metadata().unwrap().len())
.sum::<u64>() as usize;
let mut buf = Vec::<u8>::with_capacity(total_len);
for file in files {
file.read_to_end(&mut buf)?;
}
file.write_all(&buf)?;
// assert_eq!(buf.capacity(), buf.len());
Ok(())
}
fn read_files_into_file2(files: &mut Vec<fs::File>, file: &mut fs::File) -> io::Result<()> {
reset(files, file);
let total_len = files
.iter()
.map(|file| file.metadata().unwrap().len())
.sum::<u64>() as usize;
let mut vec: Vec<u8> = vec![0; total_len];
let mut buf = &mut vec[..];
for file in files {
match file.read(&mut buf) {
Ok(n) => {
buf = &mut buf[n..];
}
Err(err) if err.kind() == io::ErrorKind::Interrupted => {}
Err(err) => return Err(err),
}
}
file.write_all(&vec)?;
// assert_eq!(vec.capacity(), vec.len());
Ok(())
}
/// Creates 5 files with content "hello world" 500 times.
fn get_test_files() -> io::Result<Vec<fs::File>> {
let mut files = Vec::<fs::File>::new();
for index in 0..5 {
let mut file = fs::OpenOptions::new()
.read(true)
.write(true)
.truncate(true)
.create(true)
.open(&format!("test{}", index))?;
file.write_all("hello world".repeat(500).as_bytes())?;
files.push(file);
}
Ok(files)
}

如果您取消对read0 s的注释,那么您将看到只有read_files_into_file1(使用read_to_end(出现以下死机:

thread 'main' panicked at 'assertion failed: `(left == right)`
left: `55000`,
right: `27500`', benches/bench.rs:53:5

CCD_ 13分配的内存远远多于所需的内存,而CCD_。

尽管如此,结果表明它们的性能几乎相同(read_files_into_file1需要11.439 us,read_files_into_file2需要11.098 us(:

1                       time:   [11.417 us 11.439 us 11.463 us]               
change: [+3.7987% +3.9997% +4.1984%] (p = 0.00 < 0.05)
Performance has regressed.
Found 1 outliers among 100 measurements (1.00%)
1 (1.00%) high mild
2                       time:   [11.085 us 11.098 us 11.112 us]               
change: [+0.1255% +0.5081% +0.9545%] (p = 0.01 < 0.05)
Change within noise threshold.
Found 4 outliers among 100 measurements (4.00%)
2 (2.00%) high mild
2 (2.00%) high severe

我预计read_files_into_file2会更快,但当我增加文件大小时,它甚至显示得更慢。为什么read_files_into_file2没有达到我的期望?高效地将多个文件读取为一个文件的最佳方法是什么?

read_to_end在处理大文件时通常不是一个好主意,因为它会尝试将整个文件读取到内存中,这可能会导致交换或内存不足错误。

在linux上,假设使用read_to_end0的单线程执行应该是最快的方法,因为它包含了针对这种情况的优化。

在其他平台上,使用io::copy并将编写器端封装在BufWriter中可以控制用于复制的缓冲区大小,这将有助于分摊系统调用成本。

如果您可以使用多个线程,并且知道文件长度不变,那么您可以使用特定于平台的位置读/写方法(如read_at(并行读取多个文件,并将数据写入目标文件中的正确位置。这是否真的提供了一个加速取决于许多因素。当连接网络文件系统中的许多小文件时,这可能是最有益的。

除了标准库之外,还有一些板条箱可以公开特定于平台的复制例程,这可能比简单的用户空间复制方法更快。

相关内容

最新更新