为何更大写缓冲区下缓冲I/O比直接I/O耗时更长?
问题描述
测试I/O性能时发现一个反常现象:
- 程序1:设置4096字节的stdio流缓冲区,执行1亿次单字节写入,耗时40.806秒
struct timespec start, finish, delta; char write_buffer[4096]; char buffer_to_write[1] = {[0 ... 0] = 0x00}; FILE* fd = fopen("file.txt", "wb"); setbuf(fd, write_buffer); clock_gettime(CLOCK_REALTIME, &start); for (int i = 0; i < 100000000; i++) { fwrite(buffer_to_write, sizeof(char), sizeof(buffer_to_write), fd); } clock_gettime(CLOCK_REALTIME, &finish);
- 程序2:使用直接I/O,执行24414次4096字节缓冲区写入(数据量与程序1相近),仅耗时0.5秒
struct timespec start, finish, delta; char buffer_to_write[4096] = {[0 ... 4095] = 0x00}; int fd = open("file.txt", O_WRONLY, 0); clock_gettime(CLOCK_REALTIME, &start); for (int i = 0; i < 24414; i++) { write(fd, buffer_to_write, sizeof(buffer_to_write)); } clock_gettime(CLOCK_REALTIME, &finish);
原以为两者的系统调用量应该相同(每4096次单字节写触发一次内核写入),但无法理解为何缓冲I/O程序耗时远长。
原因分析
核心差异在于用户态的操作累积开销,而非系统调用次数。虽然两者最终触发的内核write系统调用次数相近,但用户态的执行成本天差地别:
1. 函数调用次数的数量级差距
程序1执行了1亿次fwrite调用,程序2仅执行24414次write调用。哪怕每次fwrite只比write多几纳秒的用户态逻辑(比如参数校验、缓冲区剩余空间计算),1亿次的累积开销也会达到数十秒级别,这是最主要的耗时来源。
2. 缓冲区的细碎管理成本
fwrite处理单字节写入时,每次都要执行:
- 检查缓冲区剩余空间
- 将单字节拷贝到缓冲区对应位置
- 更新写入指针与剩余字节数
- 判断是否需要触发内核写入
这些细碎操作在1亿次循环中反复执行,每一步的微小开销都会被无限放大。而直接调用write写入大块数据时,没有这些用户态的缓冲区管理步骤。
3. 编译器优化的差异
程序2的循环逻辑简单,编译器可以轻松进行循环展开、冗余操作消除等优化,大幅提升效率。但程序1的循环中调用了标准库函数fwrite,编译器无法深入优化其内部逻辑;同时单字节写入的模式也很难被合并为批量操作,优化空间极小。
4. 内存访问效率差异
单字节写入会频繁修改缓冲区的单个字节,导致CPU缓存行频繁被标记为脏,后续缓存同步操作带来额外开销。而大块连续内存的写入缓存命中率更高,内存读写效率远高于细碎的单字节操作。
验证建议
修改程序1,改用fwrite批量写入4096字节,执行24414次,此时两者的耗时会非常接近——因为用户态的函数调用次数、缓冲区管理成本完全对齐了。
内容的提问来源于stack exchange,提问作者Marco
相关产品推荐
相关产品推荐

