You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何`File::read_to_end`会随缓冲区容量增大而变慢?

注意:

截至2023年4月23日,此问题的修复已合并到rust-lang/rust:master。很快你就可以无忧使用File::read_to_end了。


问题详情

我正在处理一个特殊需求:读取数十万份从几字节到几百MB不等的文件。由于操作核心是枚举文件并从磁盘传输数据,我选择复用Vec缓冲区来避免部分内存管理开销。

这时我遇到了意外情况:file.read_to_end(&mut buffer)?的速度会随缓冲区容量增大而逐渐变慢。先读取一个300MB文件再读取一千个1KB文件,比反过来操作慢得多(只要不截断缓冲区)。

令人困惑的是,如果用Take包装文件或使用read_exact(),就不会出现速度下降的情况。

有人知道这是怎么回事吗?是否每次调用时都会(重新)初始化整个缓冲区?这是Windows特有的问题吗?处理此类问题时,推荐哪些Windows性能分析工具?

以下是一个简单的复现代码,可展示这些方法间巨大的性能差异(在我的机器上相差50倍以上,忽略磁盘速度影响):

use std::io::Read;
use std::fs::File;

// with a smaller buffer, there's basically no difference between the methods...
// const BUFFER_SIZE: usize = 2 * 1024;

// ...but the larger the Vec, the bigger the discrepancy.
// for simplicity's sake, let's assume this is a hard upper limit.
const BUFFER_SIZE: usize = 300 * 1024 * 1024;


fn naive() {
    let mut buffer = Vec::with_capacity(BUFFER_SIZE);

    for _ in 0..100 {
        let mut file = File::open("some_1kb_file.txt").expect("opening file");

        let metadata = file.metadata().expect("reading metadata");
        let len = metadata.len();
        assert!(len <= BUFFER_SIZE as u64);

        buffer.clear();
        file.read_to_end(&mut buffer).expect("reading file");

        // do "stuff" with buffer
        let check = buffer.iter().fold(0usize, |acc, x| acc.wrapping_add(*x as usize));

        println!("length: {len}, check: {check}");
    }
}

fn take() {
    let mut buffer = Vec::with_capacity(BUFFER_SIZE);

    for _ in 0..100 {
        let file = File::open("some_1kb_file.txt").expect("opening file");

        let metadata = file.metadata().expect("reading metadata");
        let len = metadata.len();
        assert!(len <= BUFFER_SIZE as u64);

        buffer.clear();
        file.take(len).read_to_end(&mut buffer).expect("reading file");

        // this also behaves like the straight `read_to_end` with a significant slowdown:
        // file.take(BUFFER_SIZE as u64).read_to_end(&mut buffer).expect("reading file");

        // do "stuff" with buffer
        let check = buffer.iter().fold(0usize, |acc, x| acc.wrapping_add(*x as usize));

        println!("length: {len}, check: {check}");
    }
}

fn exact() {
    let mut buffer = vec![0u8; BUFFER_SIZE];

    for _ in 0..100 {
        let mut file = File::open("some_1kb_file.txt").expect("opening file");

        let metadata = file.metadata().expect("reading metadata");
        let len = metadata.len() as usize;
        assert!(len <= BUFFER_SIZE);

        // SAFETY: initialized by `vec!` and within capacity by `assert!`
        unsafe { buffer.set_len(len); }
        file.read_exact(&mut buffer[0..len]).expect("reading file");

        // do "stuff" with buffer
        let check = buffer.iter().fold(0usize, |acc, x| acc.wrapping_add(*x as usize));

        println!("length: {len}, check: {check}");
    }
}

fn main() {
    let args: Vec<String> = std::env::args().collect();

    if args.len() < 2 {
        println!("usage: {} <method>", args[0]);
        return;
    }

    match args[1].as_str() {
        "naive" => naive(),
        "take" => take(),
        "exact" => exact(),
        _ => println!("Unknown method: {}", args[1]),
    }
}

我尝试了--release模式、LTO甚至+crt-static等多种组合,但均未带来明显差异。


内容的提问来源于stack exchange,提问作者ducktherapy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 11:23:25