19

几个星期以来,我一直在与我写的一个表现不佳的翻译作斗争。在以下简单的基准上

#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在最后使用了相应的流,但是遇到了关于隐式声明的编译器错误,以及声称对这些函数的未定义引用的链接器错误。

这个问题是否还有其他解释,更重要的是,有没有办法在 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这似乎是这里的慢动作,但我不知道它需要模仿什么行为使它如此缓慢。

4

4 回答 4

12

在对源代码进行了一些挖掘之后,我发现了为什么 MinGW 功能如此之慢:

[v,f,s]printf在MinGW的 a 开头,有一段看起来很无辜的初始化代码:

__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在 Windows 上一定不能很快。用 a 替换该定义2会使运行时回到应有的位置。


因此,答案归结为:当使用-std=c99或任何符合 ANSI 的模式时,MinGW 会使用自己的 CRT 运行时进行切换。通常,这不会是一个问题,但 MinGW 库有一个错误,它减慢了它的格式化功能,远远超出了任何想象。

于 2012-12-20T14:07:21.027 回答
8

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

使用 GNU 扩展和优化,您fprintf(test, "B")可能会被替换为fputc('B', test)

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

于 2012-12-20T11:28:18.950 回答
0

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

现在,MS 明显缺乏 GCC 实现的许多功能。其中一些是 GNU 扩展,而另一些则是为了符合 C99。并且由于您正在使用-std=c99您正在请求一致性。

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

它是否解释了 x10 的因子?可能不是...

您可以尝试的另一件事:

  • 替换fprintf()sprintf(), 打印到内存缓冲区而不接触文件。然后你可以尝试fwrite()使用 printfing。这样您就可以猜测丢失是在数据格式中还是在写入FILE.
于 2012-12-20T13:31:38.820 回答
0

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

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

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

于 2012-12-20T13:46:22.453 回答