使用fread&fwrite优化进程间管道100MB文件传输性能
管道传输文件优化:用fread/fwrite替代逐字符读写
核心问题根源
逐字符的getc/putc每次仅处理1字节,会触发频繁的系统调用,内核态与用户态切换开销极大,这是100MB文件耗时超2.5分钟的主要原因。
优化方案关键步骤
pipe返回的是文件描述符(fd[0]读端、fd[1]写端),要结合fread/fwrite,需先通过fdopen()将文件描述符转换为标准FILE*指针,再使用块读写操作,同时配置合适大小的缓冲区:
文件描述符转FILE*指针
- 写端:
FILE* pipe_writer = fdopen(fd[1], "w"); - 读端:
FILE* pipe_reader = fdopen(fd[0], "r");
转换后即可直接使用标准I/O库的fread/fwrite函数。
- 写端:
设置大尺寸缓冲区
默认标准I/O缓冲区较小,手动设置更大的缓冲区(如64KB、128KB)能进一步减少系统调用次数:#define BUFFER_SIZE (64 * 1024) // 64KB缓冲区 setvbuf(pipe_writer, NULL, _IOFBF, BUFFER_SIZE); // 全缓冲模式 setvbuf(pipe_reader, NULL, _IOFBF, BUFFER_SIZE);块读写循环
用fread批量读取源文件,再通过fwrite批量写入管道;读端同理,批量读取管道内容后写入目标文件。
完整优化代码示例
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #define BUFFER_SIZE (64 * 1024) // 64KB缓冲区 int main() { int fd[2]; pid_t pid; FILE *src_file, *dest_file; // 打开源文件和目标文件 src_file = fopen("source_100mb.bin", "rb"); dest_file = fopen("dest_100mb.bin", "wb"); if (!src_file || !dest_file) { perror("File open failed"); exit(EXIT_FAILURE); } // 创建管道 if (pipe(fd) == -1) { perror("Pipe failed"); exit(EXIT_FAILURE); } pid = fork(); if (pid == -1) { perror("Fork failed"); exit(EXIT_FAILURE); } if (pid == 0) { // 子进程:读管道,写目标文件 close(fd[1]); // 关闭不需要的写端 FILE* pipe_reader = fdopen(fd[0], "r"); if (!pipe_reader) { perror("fdopen reader failed"); exit(EXIT_FAILURE); } setvbuf(pipe_reader, NULL, _IOFBF, BUFFER_SIZE); char buffer[BUFFER_SIZE]; size_t bytes_read; while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, pipe_reader)) > 0) { fwrite(buffer, 1, bytes_read, dest_file); } fclose(pipe_reader); fclose(dest_file); exit(EXIT_SUCCESS); } else { // 父进程:读源文件,写管道 close(fd[0]); // 关闭不需要的读端 FILE* pipe_writer = fdopen(fd[1], "w"); if (!pipe_writer) { perror("fdopen writer failed"); exit(EXIT_FAILURE); } setvbuf(pipe_writer, NULL, _IOFBF, BUFFER_SIZE); char buffer[BUFFER_SIZE]; size_t bytes_read; while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, src_file)) > 0) { fwrite(buffer, 1, bytes_read, pipe_writer); } fclose(pipe_writer); fclose(src_file); wait(NULL); // 等待子进程完成 exit(EXIT_SUCCESS); } }
额外注意事项
- 必须关闭不需要的管道端(如父进程关闭
fd[0],子进程关闭fd[1]),否则管道不会触发EOF,导致读端无限阻塞。 - 写完管道后,关闭
FILE*指针会自动刷新缓冲区,无需手动fflush(若中途需要强制刷新可调用该函数)。 - 缓冲区大小可根据系统调整,一般4KB~128KB都能获得较好的性能,过大可能浪费内存。
内容的提问来源于stack exchange,提问作者ATB
相关产品推荐
相关产品推荐

