没有“-std = c99”的大规模fprintf速度差异

时间:2012-12-20 10:59:33

标签: c performance locking stdio mingw32

我和我写过的表现不佳的翻译一直在挣扎数周。 在以下简单的bechmark上

#include<stdio.h>

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上尝试了_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 ,但是遇到了关于隐式声明的编译器错误,以及声明未定义引用的链接器错误这些功能。

对于这个问题可能有另一种解释,更重要的是,有没有办法在Windows上使用C99而不付出如此巨大的性价?


编辑:

在查看这些选项生成的代码之后,它看起来像在慢速版本中,mingw如下所示:

__mingw_vfprintf

在快速版本中,这根本就不存在;否则,两者完全相同。我认为{{1}}似乎是这里的慢速,但我不知道它需要模仿的行为使它变得如此慢。

4 个答案:

答案 0 :(得分:9)

在对源代码进行一些挖掘之后,我发现为什么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替换该定义会使运行时回到应有的位置。


因此,答案归结为:当使用-std=c99或任何ANSI兼容模式时,MinGW会使用自己的模式切换CRT运行时。通常情况下,这不会是一个问题,但MinGW lib有一个错误,它使格式化功能的速度远远超出了可以想象的范围。

答案 1 :(得分:7)

使用-std=c99禁用所有GNU扩展。

借助GNU扩展和优化,您的fprintf(test, "B")可能会被fputc('B', test)

取代

注意此答案已过时,请参阅https://stackoverflow.com/a/13973562/611560https://stackoverflow.com/a/13973933/611560

答案 2 :(得分:0)

在对汇编程序进行一些考虑后,看起来慢速版本正在使用MinGW的*printf()实现,毫无疑问是在GCC版本中,而快速版本使用的是msvcrt.dll的Microsoft实现

现在,MS的一个特别是缺少很多功能,GCC确实实现了。其中一些是GNU扩展,但其他一些是C99一致性。由于您使用的是-std=c99,因此您要求符合。

但为什么这么慢?嗯,一个因素是简单性,MS版本要简单得多,因此预计它会运行得更快,即使是在琐碎的情况下也是如此。其他因素是您在Windows下运行,因此预计MS版本比从Unix世界复制的版本更高效。

它能解释x10的因素吗?可能不是......

你可以尝试另一件事:

  • fprintf()替换为sprintf(),打印到内存缓冲区而根本不触及该文件。然后,您可以尝试在不使用 printfing 的情况下执行fwrite()。这样您就可以猜测丢失是在数据格式中还是写入FILE

答案 3 :(得分:0)

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

有关详细信息,请参阅mingw32 changelog

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