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

是否使用缓冲区?fputc()与fwrite()性能对比及优化疑问

基准测试代码(ChatGPT生成)

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define SIZE 8388608
// #define SIZE 1073741824

int main()
{
    FILE* file;
    char* buffer = malloc(SIZE);

    // Fill buffer with some data
    for (int i = 0; i < SIZE; i++) {
        buffer[i] = 'A';
    }

    // Benchmark fwrite
    file = fopen("fwrite_test", "wb");
    clock_t start = clock();
    for (int i = 0; i < SIZE / BUFSIZ; i++) {
        fwrite(buffer + i * BUFSIZ, sizeof(char), SIZE / BUFSIZ, file);
    }
    clock_t end = clock();
    fclose(file);
    printf("fwrite: %f seconds\n", (double)(end - start) / CLOCKS_PER_SEC);

    // Benchmark fputc
    file = fopen("fputc_test", "wb");
    start = clock();
    for (int i = 0; i < SIZE; i++) {
        fputc(buffer[i], file);
    }
    end = clock();
    fclose(file);
    printf("fputc : %f seconds\n", (double)(end - start) / CLOCKS_PER_SEC);

    return 0;
}

测试结果

8MiB文件测试结果

fwrite: 0.001915 seconds
fputc : 0.092756 seconds

-> fwrite比fputc快48倍

1GiB文件测试结果

fwrite: 14.579728 seconds
fputc : 3.512994 seconds

-> fputc比fwrite快4倍


实际待优化代码

#include "bitHandler.h"
#include <stdint.h>
#include <stdio.h>

// Struct with all needed Infos to write buffered bits to a file
typedef struct bitBuffer {
    unsigned char* buffer;
    unsigned int bufferPosition;
    int bitPosition;
    FILE* output;
} bitBuffer_t;

// UINT_FAST64_WIDTH is defined in c23, assuming 64 is reaasonable if compiler doesn't support c23
#ifndef UINT_FAST64_WIDTH
#define UINT_FAST64_WIDTH 64
#endif

// Write a single bit to buffer and then to file
extern void writeBit(int bit, bitBuffer_t* bitBuffer)
{
    if (bit) {
        bitBuffer->buffer[bitBuffer->bufferPosition] |= 1u << (7 - bitBuffer->bitPosition);
    } else {
        bitBuffer->buffer[bitBuffer->bufferPosition] &= ~(1u << (7 - bitBuffer->bitPosition));
    }
    // Go to next byte if current one is full, flush buffer if it is full
    if (bitBuffer->bitPosition == 7) {
        if (bitBuffer->bufferPosition == BUFSIZ - 1) {
            flush(bitBuffer);
        } else {
            ++bitBuffer->bufferPosition;
        }
        bitBuffer->bitPosition = 0;
    } else {
        ++bitBuffer->bitPosition;
    }
}

// write multiple bits
extern void writeBits(uint_fast64_t bits, int nbits, bitBuffer_t* bitBuffer)
{
    int i;
    int bit;
    for (i = 0; i < nbits; i++) {
        bit = (bits >> (UINT_FAST64_WIDTH - 1 - i)) & 1u;
        writeBit(bit, bitBuffer);
    }
}

// write byte
extern void writeByte(unsigned char byte, bitBuffer_t* bitBuffer)
{
    int i;
    for (i = 0; i < 8; i++) {
        writeBit((byte >> (7 - i)) & 1u, bitBuffer);
    }
}

// flush the remaining bits in buffer to file
extern void flush(bitBuffer_t* bitBuffer)
{
    fwrite(bitBuffer->buffer, sizeof(char),
        (bitBuffer->bitPosition == 0) ? bitBuffer->bufferPosition : bitBuffer->bufferPosition + 1,
        bitBuffer->output);
    bitBuffer->bufferPosition = 0;
    bitBuffer->bitPosition = 0;
}

问题解答

1. fputc()与fwrite()性能差异的原因

首先要明确:你的基准测试代码存在致命错误——fwrite循环里的第三个参数写错了,应该是BUFSIZ而不是SIZE / BUFSIZ。原代码中,每次fwrite都试图写入SIZE/BUFSIZ字节,总写入量变成了(SIZE/BUFSIZ)*(SIZE/BUFSIZ),远超出预期的SIZE,完全扭曲了测试结果。修正后,fwrite的性能会始终优于fputc,这才符合常规IO性能逻辑。

如果忽略这个错误假设结果真实,可能的解释是:

  • 小文件场景:fputc每次调用都要承担函数调用、单字节缓冲区管理的开销,而fwrite一次写入大块数据,函数调用次数极少,开销占比低,因此更快。
  • 大文件场景:错误的fwrite写法导致超量写入,引发磁盘IO过载;而fputc依赖C标准库默认缓冲区(通常也是BUFSIZ大小),实际底层IO次数和正确的fwrite相当,但错误的fwrite逻辑带来了额外的内存拷贝或磁盘压力,反而变慢。

但本质上,正确使用时,fwrite的性能不可能低于fputc——因为fputc内部也是基于缓冲区的,最终还是会调用类似fwrite的逻辑批量写入磁盘,额外多了一层单字节处理的函数调用和循环开销。

2. 追求IO最佳性能时,是否应该使用缓冲区

答案是必须使用缓冲区,但要注意正确的使用方式:

  • C标准库本身已经为FILE*对象提供了用户空间缓冲区(默认开启),fputc、fwrite都会先写入这个缓冲区,满了才会调用系统调用写入磁盘。
  • 你自己实现的位缓冲区是必要的:哈夫曼编码是变长位流,无法直接用字节级的标准库缓冲区处理,必须先把位流组装成字节,再批量写入。

针对你的哈夫曼代码,优化方向包括:

  • 重构writeBits和writeByte,避免循环调用writeBit——直接操作位缓冲区,一次性写入多比特或整字节,减少函数调用和循环开销。
  • 确保自定义缓冲区大小BUFSIZ和标准库缓冲区大小匹配,或者设置为更大的数值(比如4KiB、16KiB,对应磁盘扇区或文件系统块大小),减少底层系统调用次数。
  • 避免频繁调用flush,只有在缓冲区满或者程序结束时才刷新,减少磁盘IO次数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 16:40:53