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

Dav*_*ave 19 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
}
Run Code Online (Sandbox Code Playgroud)

我们看到以下结果

bash-3.1$ gcc -O2 -static test.c -o test
bash-3.1$ time ./test

real    0m0.334s
user    0m0.015s
sys     0m0.016s
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,在添加"-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
Run Code Online (Sandbox Code Playgroud)

我正在使用的编译器是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 
Run Code Online (Sandbox Code Playgroud)

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

Dav*_*ave 11

在深入挖掘源代码后,我发现为什么MinGW函数非常慢:

[v,f,s]printfMinGW 的开头,有一些看似无辜的初始化代码:

__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   */
};
Run Code Online (Sandbox Code Playgroud)

但是,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
    ;
}
Run Code Online (Sandbox Code Playgroud)

每当我想要打印时,这就会被调用,并且getenv在窗口上一定不能很快.用a替换define定义2将运行时恢复到应该的位置.


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


Yan*_*aud 8

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

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

请注意,此答案已过时,请参阅/sf/answers/978149371//sf/answers/978175341/

  • 情节变粗:`gnu99`很快,但`fprintf(test,"%04d \n",x)`在`gnu99`上仍然很快而在'c99`中慢. (3认同)
  • @Dave @rodrigo无需消息,只需运行`gcc -std = c99 -O2 -S file.c`,它将生成`file.s`程序集文件.您还可以使用`gcc -v -std = c99 -O2 file.c -o file`来检查链接器参数 (3认同)
  • @ydroneaud - 是的,但我发现`objdump -S`更容易阅读(因为asm与源混合).无论如何,我已经完成了测试c99与gnu99对比没有,用"%04d",GCC 4.7.2并没有可衡量的性能差异. (2认同)