为何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
疑问
- 为何单线程时SSD活跃时间未达100%?
std::filebuf::sgetn为何占用CPU?- 有无提升单线程读取性能的方法?
测试代码
#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
相关产品推荐
相关产品推荐

