c语言 - 无"-std=c99"的巨大冲刺速度差



我已经与我写过的一个表现不佳的翻译挣扎了数周。在以下简单的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,但对隐式声明的编译器错误和链接器错误,声称对这些函数的不确定引用。

是否有另一个解释此问题,更重要的是,是否有任何方法可以在Windows上使用C99而不支付如此巨大的性能价格?


编辑:

查看了这些选项生成的代码后,在慢版本中,mingw stick在以下内容中:

_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似乎是这里的慢速Poke,但是我不知道它需要采取什么行为,这使它变得如此慢。

在挖掘源代码后,我发现了为什么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
    ;
}

每次我要打印时都会被调用,并且Windows上的getenv必须很快。用2替换该定义将运行时返回到应该的位置。


因此,答案归结为:使用-std=c99或任何符合ANSI的模式时,MINGW可以自己切换CRT运行时。通常,这不是一个问题,但是Mingw lib的错误使其格式放慢的功能远远超出了可想象的任何内容。

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

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

替换

注意此答案已过时,请参见https://stackoverflow.com/a/13973562/611560和https://stackoverflow.com/a/a/139739333/611560/611560/p>Div>

在考虑了您的汇编程序后,看起来慢版本正在使用Mingw的*printf()实现,无疑是在GCC One中基于的,而快速版本则使用msvcrt.dll的Microsoft实现。

现在,MS ONE特别是由于缺乏很多功能,即GCC确实实现了。其中一些是GNU扩展,但另一些则是C99符合的。而且,由于您使用的是-std=c99,因此请求符合性。

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

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

您可以尝试的另一件事:

  • sprintf()替换fprintf(),将其打印到内存缓冲区中,而无需触摸文件。然后,您可以尝试在没有 printfing 的情况下进行fwrite()。这样,您可以猜测损失是在数据的格式化还是在FILE的书写中。

由于Mingw32 3.15,因此可以使用兼容的printf功能,而不是在Microsoft C Runtime(CRT)中找到的功能。在严格的ANSI,POSIX和/或C99模式中编译时,使用了新的printf功能。

有关更多信息,请参见Mingw32 ChangElog

您可以使用__msvcrt_fprintf()使用 fast (非兼容)函数。

最新更新