【问题标题】:Massive fprintf speed difference without "-std=c99"没有“-std=c99”的巨大 fprintf 速度差异
【发布时间】: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在快速版本中不存在。但是,主循环是什么样的呢?

标签: c performance locking stdio mingw32


【解决方案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://stackoverflow.com/a/13973562/611560https://stackoverflow.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() 来使用快速(不符合)功能。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-07-06
      • 2019-12-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-28
      相关资源
      最近更新 更多