微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决将多个文件读入一个的最佳方法是什么?

这是一个比较两个函数的基准,这些函数将多个文件读入一个文件一个使用read,另一个使用read_to_end。我最初的动机是在进程结束时让缓冲区的 capacity 等于 lenread_to_end 没有发生这种情况,这很不令人满意。

然而,使用 read,这是有效的。 assert_eq!(buf.capacity(),buf.len());read_files_into_file2(使用 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);

    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)
}

如果您取消对 assert_eq! 的注释,那么您将看到只有 read_files_into_file1(使用 read_to_end)会因此恐慌失败:

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

read_files_into_file1 分配比需要更多的内存,而 read_files_into_file2 分配最佳数量

尽管如此,结果表明它们的性能几乎相同(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 上并假设使用 io::copy 单线程执行应该是最快的方法,因为它包含 optimizations for this case

在其他平台上使用 io::copy 并将写入端包装在用于复制的 BufWriter lets you control the buffer size 中,这将有助于分摊系统调用成本。

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。