没有“-std = c99”的大规模fprintf速度差异
我写过一部表现不佳的翻译,几周来我一直在挣扎。 在下面简单的bechmark
#include int main() { int x; char buf[2048]; FILE *test = fopen("test.out", "wb"); setvbuf(test, buf, _IOFBF, sizeof buf); for(x=0;x<1024*1024; x++) fprintf(test, "%04d", x); fclose(test); return 0 }
我们看到以下结果
bash-3.1$ gcc -O2 -static test.c -o test bash-3.1$ time ./test real 0m0.334s user 0m0.015s sys 0m0.016s
正如您所看到的,在添加“-std = c99”标志的那一刻,性能崩溃了:
bash-3.1$ gcc -O2 -static -std=c99 test.c -o test bash-3.1$ time ./test real 0m2.477s user 0m0.015s sys 0m0.000s
我正在使用的编译器是gcc 4.6.2 mingw32。
生成的文件大约是12M,因此两者之间的差异大约为21MB / s。
运行diff
显示生成的文件是相同的。
我认为这与fprintf
文件锁定有关,程序大量使用,但我无法找到在C99版本中关闭它的方法。
我在程序开头使用的流上尝试了flockfile
,并在最后使用了相应的funlockfile
,但是funlockfile
了关于隐式声明的编译器错误,以及声称对这些函数的未定义引用的链接器错误。
是否可以对此问题进行另一种解释,更重要的是,有没有办法在Windows上使用C99而无需支付如此巨大的性能价格?
编辑:
在查看这些选项生成的代码之后,它看起来像在慢速版本中,mingw如下所示:
_fprintf: LFB0: .cfi_startproc subl $28, %esp .cfi_def_cfa_offset 32 leal 40(%esp), %eax movl %eax, 8(%esp) movl 36(%esp), %eax movl %eax, 4(%esp) movl 32(%esp), %eax movl %eax, (%esp) call ___mingw_vfprintf addl $28, %esp .cfi_def_cfa_offset 4 ret .cfi_endproc
在快速版本中,这根本不存在; 否则,两者完全相同。 我假设__mingw_vfprintf
似乎是这里的__mingw_vfprintf
,但我不知道它需要模拟哪种行为使它变得如此缓慢。
在对源代码进行一些挖掘之后,我发现为什么MinGW函数非常慢:
在MinGW中[v,f,s]printf
的开头,有一些看似无辜的初始化代码:
__pformat_t stream = { dest, /* output goes to here */ flags &= PFORMAT_TO_FILE | PFORMAT_NOLIMIT, /* only these valid initially */ PFORMAT_IGNORE, /* no field width yet */ PFORMAT_IGNORE, /* nor any precision spec */ PFORMAT_RPINIT, /* radix point uninitialised */ (wchar_t)(0), /* leave it unspecified */ 0, /* zero output char count */ max, /* establish output limit */ PFORMAT_MINEXP /* exponent chars preferred */ };
但是, PFORMAT_MINEXP
并不像它看起来那样:
#ifdef _WIN32 # define PFORMAT_MINEXP __pformat_exponent_digits() # ifndef _TWO_DIGIT_EXPONENT # define _get_output_format() 0 # define _TWO_DIGIT_EXPONENT 1 # endif static __inline__ __attribute__((__always_inline__)) int __pformat_exponent_digits( void ) { char *exponent_digits = getenv( "PRINTF_EXPONENT_DIGITS" ); return ((exponent_digits != NULL) && ((unsigned)(*exponent_digits - '0') < 3)) || (_get_output_format() & _TWO_DIGIT_EXPONENT) ? 2 : 3 ; }
每当我想要打印时,这就会被调用,并且窗口上的getenv
一定不能很快。 用2
替换define定义会使运行时返回到应该的位置。
因此,答案归结为:当使用-std=c99
或任何ANSI兼容模式时,MinGW会自行切换CRT运行时。 通常情况下,这不是问题,但MinGW lib有一个错误,它的格式化function远远超出了任何想象的范围。
使用-std=c99
禁用所有GNU扩展。
使用GNU扩展和优化,你的fprintf(test, "B")
可能被fputc('B', test)
取代
请注意,此答案已过时,请参阅https://stackoverflow.com/a/13973562/611560和https://stackoverflow.com/a/13973933/611560
在对汇编程序进行一些考虑之后,看起来慢速版本正在使用MinGW的*printf()
实现,毫无疑问是在GCC版本中,而快速版本则使用msvcrt.dll
的Microsoft实现。
现在,MS的一个特别是缺少很多function,GCC确实实现了。 其中一些是GNU扩展,但其他一些是C99一致性。 由于您使用的是-std=c99
您要求符合性。
但为什么这么慢? 嗯,一个因素是简单性,MS版本要简单得多,因此预计它会运行得更快,即使在简单的情况下也是如此。 其他因素是您在Windows下运行,因此预计MS版本比从Unix世界复制的版本更高效。
它能解释x10的因素吗? 可能不是…
你可以尝试的另一件事:
- 用
sprintf()
替换fprintf()
,打印到内存缓冲区而根本不触摸文件。 然后你可以尝试不使用printfing做fwrite()
。 这样你可以猜测丢失是在数据的格式化还是写入FILE
。
从MinGW32 3.15开始,可以使用兼容的printf
函数,而不是Microsoft C运行时(CRT)中的函数。 在严格的ANSI,POSIX和/或C99模式下编译时使用新的printf
函数。
有关更多信息,请参阅mingw32更改日志
您可以使用__msvcrt_fprintf()
来使用快速 (非兼容)function。