【问题标题】:How to write text file faster than fprintf in c?如何在 c 中比 fprintf 更快地写入文本文件?
【发布时间】:2018-06-05 18:02:45
【问题描述】:

我必须将一些图形数据(结构数组)保存到文本文件中。我使用 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);
    }
}

【问题讨论】:

  • 您确定写入文件部分是您速度变慢的原因吗?
  • 比 fprintf 快吗?你在做什么?遍历图形时可能存在缓存未命中?你的cpu在什么负载下?您如何定义 比 fprintf 更快 向我们展示您的尝试和期望?如果您没有实际实施替代方案,谷歌搜索没有任何用处? fwrite 用于二进制写入。
  • 如以上海报所述,如果您需要性能帮助,请提供性能分析结果。这个问题可能没有那么简单,以至于无法猜测给出的内容。似乎graph->edges[i] 中可能存在三个内存访问,但这很可能会保留在 L1 缓存中。 fprintf 显然是一种矫枉过正,因为您的格式很简单。但这些只是可能完全错误的可能性。
  • 您是否对代码进行了概要分析以了解您实际花费的时间?如果你不这样做,你只是在猜测你需要做什么才能更快。
  • 加快 I/O 处理的方法是:1) 在内存中格式化数据而不是在fprintf() 2) 累积数据的块/数组,然后将块/数组写入一个电话到write()。建议块/数组大小为 4096 字节,

标签: c performance file text text-files


【解决方案1】:

我会尝试在流上设置写入缓冲区,并尝试不同大小的缓冲区(例如 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);

    ...

输出文件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;
    }
}

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

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);
}

更新

通过将 my_putchar 函数声明为 inline 并使用 4K 缓冲区,上述代码(使用从随机整数数组读取的图形模拟修改)比 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)

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

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

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

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

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

【讨论】:

  • 我认为它可以工作,但我真的不明白如何编码。谢谢,我会尝试使用它。
  • 您只需添加setvbuf 行,并将BUFFERSIZE 定义放在文件顶部(并将buf 分配在写入函数中)。
  • 缓冲通过减少“实际”I/O 操作的数量来帮助加快速度,因此缩小缓冲区不是一个好主意。
  • 其中大约 2 倍似乎来自缓冲(我本来预计会更多) 我很惊讶你从 4K fprintf() 缓冲中获得了任何加速。我希望默认缓冲区是 4K 或 8K。根据我的经验,stdio 函数的默认缓冲区大小通常提供了大多数磁盘系统上最大可能性能的 80-90%,而显着提高实际 IO 性能的唯一方法是针对特定文件系统和硬件调整代码配置。我发现性能提升在于避免printf() 转换。在慢速磁盘上,这无关紧要。
  • 。 @AndrewHenle 我的比较代码中有一个错误,我使用了错误的基线。总而言之,我可能应该收回这个答案,因为它不会为 Kiwi 添加任何相关和重要的内容,除非确认它有效。
【解决方案2】:

我会写一个小函数说 print_graph(int int int) 并直接在里面调用write

或类似 my_putchar 的写调用

int     my_put_nbr(int nb)
{
if (nb < 0)
{
    my_putchar('-');
    nb = -nb;
}
if (nb <= 9)
    my_putchar(nb + 48);
else
{
    my_put_nbr(nb / 10);
    my_put_nbr(nb % 10);
}
return (0);
}

【讨论】:

  • 点赞一个聪明的小功能;我不太确定它是否真的比 printf 快,但值得一试。
【解决方案3】:

我必须比 fprintf 快 1.3 倍,这是对我有用的代码。我不得不说我必须多次提交它,有时我使用相同的代码只通过了五分之一的测试。总之,它比 fprintf 快,但不能可靠地快 1.3 倍..

void save_txt(const graph_t * const graph, const char *fname)
    {
        int count = graph->num_edges, i = 0;
        char c = '\n';
        char d = ' ';
        char buffer[15];
        FILE *f = fopen(fname, "w");
        while (count > 0) {
            itoa(graph->edges[i].from,buffer,10);
            fputs(buffer, f);
            putc(d, f);
            itoa(graph->edges[i].to,buffer,10);
            fputs(buffer, f);
            putc(d, f);
            itoa(graph->edges[i].cost,buffer,10);
            fputs(buffer, f);
            putc(c, f);
            i++;
            count -= 1;
        }
        if (f) {
            fclose(f);
        } 
    }

【讨论】:

  • 如果这个语句:FILE *f = fopen(fname, "w"); 失败,那么下面的代码会出现段错误。建议遵循该声明:if( !f ) { perror( "fopen failed" ); exit( EXIT_FAILURE ); }
  • 建议使用sprintf() 将所有一行输出放入一个缓冲区,然后只调用一次fputs() 而根本不调用putc()
猜你喜欢
  • 2020-07-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多