fputwc 和 fgetwc 是否比 fprintf 和 fscanf 快得多?为什么会这样?

Are fputwc and fgetwc significantly faster than fprintf and fscanf? Why is it so?

我正在使用 wcahr_t 类型的字符处理文件。我想让我的程序更快。

尽可能将 fprintf 替换为 fputwc 并将 fscanf 替换为 fgetwc 是否有意义?

如果是,为什么?

我每次调用仅使用一个 wchar_t 进行了一些快速分析,确实存在明显的差异。一、代码:

#include "stdio.h"
#include "time.h"
#include "wchar.h"

wchar_t text[] = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#$";
const int N = 64;
const int M = 500000;
int main()
{
    int i, j;
    FILE *f1 = fopen("out1", "w");
    FILE *f2 = fopen("out2", "w");

    if (f1 == NULL || f2 == NULL)
    {
        printf("Failed to create file.\n");
        return -1;
    }

    clock_t time1 = clock();
    for (i = 0; i < M; i++)
        for (j = 0; j < N; j++)
            fprintf(f1, "%c", text[j]);
    time1 = clock() - time1;


    clock_t time2 = clock();
    for (i = 0; i < M; i++)
        for (j = 0; j < N; j++)
            fputwc(text[j], f2);
    time2 = clock() - time2;

    printf("fprintf: %d ticks\nfputwc: %d ticks\n", time1, time2);
    return 0;
}

gcc 的输出:

fprintf: 5663 ticks

fputwc: 3307 ticks

clang 的输出:

fprintf: 4696 ticks

fputwc: 3338 ticks


写stdout和内存buffer的时间差是一样的,所以这和他们怎么写,什么时候flush等没有关系,只是函数实现而已。让我们来看看它们:

最明显的区别是 fprintf 采用一种必须解析的格式。因此,它必须做的最少工作是:

while (*fmt) {
    if (*fmt++ == '%') {
        switch (*fmt)
        {
        case 'c': *output = va_arg(args, int); break;
        ...
      }
    }
}

如您所见,代码必须执行多项 fputwc 不需要的检查和递增,这意味着额外的执行时间。 case 'c'在其他情况下的位置实际上很重要,它越低,速度越慢。(见评论)这些差异可能会占大部分额外时间.

特别是在 printf("%c", x) 的情况下,我们有:

  • 设置初始状态 - 比方说 5-30 条指令
  • 读取第一个字符并与'%'比较——大约5条指令
  • 读取第二个字符,switch 语句 - 大约 10 条指令。

在此之后,它做的事情与 fputwc 几乎相同。每次调用的所有这些额外指令加起来。

还有一些其他因素会对此产生影响,例如传递一个额外的参数,但一个好的编译器可以优化它。


综上所述,这是一种非常低效的处理方式。您可能应该写入缓冲区,然后调用 fwrite(我将其计时为类似于 fputwc)并在一次调用中转储整个缓冲区。需要注意的一件事是 fwrite(f, sizeof(wchar_t), N, text); 实际上也会输出 0 个字节,以 "a[=22=]b[=22=]c[=22=]" 结束,依此类推。