没有“-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() ,打印到内存缓冲区而根本不触摸文件。 然后你可以尝试不使用printfingfwrite() 。 这样你可以猜测丢失是在数据的格式化还是写入FILE

从MinGW32 3.15开始,可以使用兼容的printf函数,而不是Microsoft C运行时(CRT)中的函数。 在严格的ANSI,POSIX和/或C99模式下编译时使用新的printf函数。

有关更多信息,请参阅mingw32更改日志

您可以使用__msvcrt_fprintf()来使用快速 (非兼容)function。