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=]"
结束,依此类推。
我正在使用 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=]"
结束,依此类推。