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

为何SSD上多线程分块读取大文件比单线程顺序读取更快?

大文件读取:单线程vs多线程的性能差异分析

背景与实测结果

StackOverflow上普遍共识是读取完整大文件时,顺序读取速度最快,多线程读取无法带来收益,但实测结果却相反:

  • 100GB文件:单线程耗时41秒,多线程耗时27秒
  • 1000GB文件:多线程速度比单线程快2至3倍

测试环境:

  • 存储:三星SSD 990 PRO 4TB
  • CPU:Xeon w9-3495X 56核
  • 资源占用:
    • 单线程:SSD活跃时间约75%,CPU负载约2%
    • 4线程:CPU负载约7%,接近100% / 56 * nThreads

疑问

  1. 为何单线程时SSD活跃时间未达100%?
  2. std::filebuf::sgetn为何占用CPU?
  3. 有无提升单线程读取性能的方法?

测试代码

#include <chrono>
#include <fstream>
#include <ios>
#include <iostream>
#include <memory>
#include <thread>
#include <vector>

// fsutil file createnew 100GB 100000000000
constexpr auto filename = "100GB";
constexpr auto bufferSize = 6'000'000;
constexpr auto nThreads = 4;

template<typename Callback>
void timeit(const char * message, const Callback & callback) {
    using namespace std::chrono;

    std::cout << message << ": ";
    const auto start = high_resolution_clock::now();

    callback();

    std::cout << duration_cast<seconds>(high_resolution_clock::now() - start) << std::endl;
}

static void readFile(const size_t nThreads = 1, const size_t iThread = 0) {
    std::filebuf file;
    file.open(filename, std::ios::in | std::ios::binary);

    const auto buffer = std::make_unique_for_overwrite<char[]>(bufferSize);

    if (iThread > 0) {
        file.pubseekoff(iThread * bufferSize, std::ios_base::cur);
    }
    while (file.sgetn(buffer.get(), bufferSize)) {
        if (nThreads > 1) {
            file.pubseekoff((nThreads - 1) * bufferSize, std::ios_base::cur);
        }
    }
}

int main() {
    timeit("sequential", [] { readFile(); });

    timeit("parallel", [] {
        std::vector<std::jthread> threads;
        for (int iThread = 0; iThread < nThreads; iThread++) {
            threads.emplace_back(readFile, nThreads, iThread);
        }
    });
}

问题解答

1. 单线程时SSD活跃时间未达100%的原因

  • NVMe SSD并行架构限制:现代NVMe SSD拥有多通道和多NAND die,单线程顺序读只能发起少量IO请求,无法填满SSD内部的IO队列,导致部分硬件单元处于空闲状态,整体利用率受限。
  • IO请求间隙开销:单线程下,每次IO完成后需要从内核态切换回用户态发起下一次请求,这个切换间隙会让SSD短暂空闲,累积后表现为活跃率不足。
  • 文件系统后台操作:即使是只读场景,文件系统(如NTFS)可能在后台处理元数据维护(比如MFT更新),偶尔抢占IO带宽,影响SSD持续利用率。

2. std::filebuf::sgetn占用CPU的原因

  • 标准库封装开销:std::filebuf是C++标准库的高层IO封装,内部包含缓冲区管理、错误检查、状态更新等逻辑,这些操作需要CPU执行,尤其是每次调用sgetn时的边界校验、指针移动等步骤。
  • 系统调用切换开销:sgetn最终会触发read类系统调用,用户态到内核态的切换、内核内部的IO请求调度都要消耗CPU资源。
  • 内存操作开销:即使没有额外内存拷贝,缓冲区的地址计算、内存分配后的地址处理也会占用少量CPU。

3. 提升单线程读取性能的方法

  • 改用底层系统IO API:跳过C++标准库,直接调用Windows的ReadFile(配合FILE_FLAG_SEQUENTIAL_SCAN和FILE_FLAG_NO_BUFFERING)或Linux的open+read,减少封装层开销。
  • 增大IO请求大小:将缓冲区从6MB调整到32MB或64MB,减少系统调用次数,提升IO队列深度,充分利用SSD的并行处理能力。
  • 启用异步IO:单线程下使用异步IO机制(如Windows IOCP、Linux io_uring),在等待当前IO完成时发起下一个请求,填满SSD的IO队列,消除请求间隙。
  • 优化文件系统设置:禁用NTFS索引功能、关闭不必要的后台元数据操作,或改用更适合大文件的文件系统(如ReFS)。
  • 调整系统IO调度器:Linux下将调度器设置为mq-deadline或none(针对NVMe);Windows确保NVMe驱动为最新版本,开启厂商提供的SSD优化选项。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 15:35:08