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

Posted

技术标签:

【中文标题】没有“-std=c99”的巨大 fprintf 速度差异【英文标题】:Massive fprintf speed difference without "-std=c99" 【发布时间】:2012-12-07 21:14:10 【问题描述】:

数周以来,我一直在与自己编写的翻译不佳的翻译作斗争。 在以下简单的基准上

#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 似乎是这里的慢动作,但我不知道它需要模拟什么行为让它变得如此缓慢。

【问题讨论】:

您需要将问题分解为一个完整的您可以实际发布的代码的工作示例,让人们推测您可能做了什么并不是很有效率。跨度> 注释掉 printf 并重新测试,看看这是否真的是差异的根源。 @unwind 我很抱歉;我确信问题出在printf 我违反了这个网站的基本规则。我什至没有想到问题会出在其他地方。 快速版本的汇编器呢?这将有助于比较。 好的,所以_fprintf在快速版本中不存在。但是,主循环是什么样的呢? 【参考方案1】:

在对源代码进行了一番挖掘之后,我发现了为什么 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 库有一个错误,它会减慢它的格式化功能,远远超出任何想象。

【讨论】:

您可以向 mingw32 开发人员报告错误。 我打算。我正在整理一个补丁,希望如果我已经准备好解决方案,他们会更容易接受。 此行为记录在我在其他答案中链接的更改日志中 getenv() 如果在运行时初始化一次就可以了 但事实并非如此。每次调用格式函数时都会执行此操作。它应该是 pre-main libc init goo 中的一个钩子。【参考方案2】:

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

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

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

【讨论】:

所以 `-std=gnu99' 是一个公平的比较,不是吗? 情节变粗了:gnu99 很快,但fprintf(test, "%04d\n", x)gnu99 上仍然很快,在c99 上很慢。 @Dave - 然后反汇编并查看生成的代码。那里的区别应该很明显。 @Dave @rodrigo 不需要解散,只需运行gcc -std=c99 -O2 -S file.c,它会生成file.s 汇编文件。您也可以使用gcc -v -std=c99 -O2 file.c -o file 来检查链接器参数 @ydroneaud - 是的,但我发现objdump -S 更易于阅读(因为 asm 与源代码混合在一起)。无论如何,我已经用"%04d",GCC 4.7.2 测试了 c99 vs. gnu99 vs nothing,并且没有可测量的性能差异。【参考方案3】:

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

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

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

它是否解释了 x10 的因数?应该不会吧……

你可以尝试的另一件事:

fprintf() 替换为sprintf(),打印到内存缓冲区而不接触文件。然后你可以尝试在没有printfing的情况下做fwrite()。这样您就可以猜测丢失是在数据格式中还是在写入FILE 中。

【讨论】:

【参考方案4】:

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

欲了解更多信息,请参阅mingw32 changelog

您可以使用__msvcrt_fprintf() 来使用快速(不符合)功能。

【讨论】:

以上是关于没有“-std=c99”的巨大 fprintf 速度差异的主要内容,如果未能解决你的问题,请参考以下文章

在 GCC 中设置 std=c99 标志

安装Libsvm工具箱出现问题E:\MATLAB7\BIN\WIN32\MEX.PL: Error: 'CFLAGS=\$CFLAGS -std=c99

在 Linux 终端中编译 C 代码

设置 C 标准标志启用 GNU 扩展

报错storage size of ‘act’ isn’t known当使用std=c99编译struct sigaction

vscode支持c99标准