如何在c中比fprintf更快地写入文本文件?

wut*_*wut 4 c performance text file text-files

我必须将一些图形数据(结构数组)保存到文本文件中。我使用 fprintf 制作了工作程序,但为了额外的分数,我需要更快。我花了几个小时谷歌搜索是否有更快的东西并尝试使用 fwrite (但我无法将 fwrite 作为文本)我真的找不到任何其他功能等。

这是我使用 fprintf 的写入函数:

void save_txt(const graph_t * const graph, const char *fname)
{
    int count = graph->num_edges, i = 0;
    FILE *f = fopen(fname, "w");
    while (count > 0) {
        int r = fprintf(f, "%d %d %d\n", (graph->edges[i].from), (graph->edges[i].to), (graph->edges[i].cost));
        i++;
        if (r >= 6) {
            count -= 1;
        } else {
            break;
        }
    }
    if (f) {
        fclose(f);
    }
}
Run Code Online (Sandbox Code Playgroud)

LSe*_*rni 5

我会尝试在流上设置写入缓冲区,并尝试不同大小的缓冲区(例如 1K、2K、4K、8K 等)。请注意,默认情况下您的文件已经使用 BUFSIZ 值的缓冲区,并且可能已经足够了。

#define BUFFERSIZE 0x1000

void save_txt(const graph_t * const graph, const char *fname)
{
    int count = graph->num_edges, i = 0;
    unsigned char buf[BUFFERSIZE];

    FILE *f = fopen(fname, "w");
    setvbuf(f, buf, _IOFBF, BUFFERSIZE);

    ...
Run Code Online (Sandbox Code Playgroud)

输出文件f天生带有默认的 BUFSIZ 缓存,因此它可能会受益于更大的全缓冲写入缓存。

当然,这假设您正在写入相对较慢的介质,并且节省的时间是相关的;否则,任何拖慢你速度的东西都不在这里,因此提高保存性能不会对你有明显帮助。

有类似profgprof之类的工具可以帮助您确定程序在哪里花费了最多的时间。

一种更尴尬的可能性是将 Kiwi 的答案与缓冲写入调用合并,以避免 printf 中验证要使用哪种格式的代码,因为您已经知道这一点,并使用尽可能少的 I/O 调用(即使只是一个如果 BUFFERSIZE 大于目标文件的长度)。

// These variables must now be global, declared outside save_txt.
char kiwiBuf[BUFFERSIZE];
size_t kiwiPtr = 0;
FILE *f;

void my_putchar(char c) {
    kiwiBuf[kiwiPtr++] = c;
    // Is the buffer full?
    if (kiwiPtr == BUFFERSIZE) {
        // Yes, empty the buffer into the file.
        flushBuffer();
    }
}

void flushBuffer() {
    if (kiwiPtr) {
        fwrite(kiwiBuf, kiwiPtr, 1, f);
        kiwiPtr = 0;
    }
}
Run Code Online (Sandbox Code Playgroud)

您现在需要在关闭之前刷新缓冲区:

void save_txt(const graph_t * const graph, const char *fname)
{
    int i, count = graph->num_edges;
    f = fopen(fname, "w");
    if (NULL == f) {
        fprintf(stderr, "Error opening %s\n", fname);
        exit(-1);
    }
    for (i = 0; i < count; i++) {
        my_put_nbr(graph->edges[i].from);
        my_putchar(' ');
        my_put_nbr(graph->edges[i].to);
        my_putchar(' ');
        my_put_nbr(graph->edges[i].cost);
        my_putchar('\n');
    }
    flushBuffer();
    fclose(f);
}
Run Code Online (Sandbox Code Playgroud)

更新

通过将my_putchar函数声明为inline4K 缓冲区,上面的代码(使用从随机整数数组读取图形的模拟进行修改)比fprintf上面的代码快大约 6 倍

Linux mintaka 4.12.8-1-default #1 SMP PREEMPT Thu Aug 17 05:30:12 UTC 2017 (4d7933a) x86_64 x86_64 x86_64 GNU/Linux
gcc version 7.1.1 20170629 [gcc-7-branch revision 249772] (SUSE Linux)
Run Code Online (Sandbox Code Playgroud)

其中大约 2 倍似乎来自缓冲。Andrew Henle 让我注意到代码中的一个错误:我将结果与无缓冲输出的基线进行比较,但fopen默认使用 BUFSIZ 值,在我的系统上 BUFSIZ 是 8192。所以基本上我“发现”了这一点:

  • 8K缓存没有优势,4K就够了
  • 我最初使用 _IOFBF 的建议完全没有价值,因为系统已经为你做到了。这反过来意味着Kiwi 的答案是最正确的,因为正如 Andrew 指出的那样,避免了printf检查和转换。

此外,总体增长(谷歌阿姆达尔定律)取决于用于保存的处理时间的比例。显然,如果一小时的阐述需要一秒钟的保存,那么将保存速度加倍可以节省半秒;而将精细化速度提高 1% 可以节省 36 秒,即 72 倍。

我自己的示例代码被设计为完全面向保存,具有非常大的图形;在这种情况下,写入速度的任何微小改进都可能带来巨大的回报,这在现实世界中可能是不现实的。

另外(回答评论),虽然使用足够小的缓冲区会减慢保存速度,但完全不确定使用较大的缓冲区是否会受益。假设整个图总共生成 1.2Kb 的输出;那么当然任何高于 1.2Kb 的缓冲区值都不会产生任何改进。实际上,分配更多内存可能会对性能产生负面影响。