巨大的fprintf速度差,无-标准=c99“;

巨大的fprintf速度差,无-标准=c99“;,c,performance,locking,stdio,mingw32,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,

几周来,我一直在与一位表现不佳的翻译进行斗争。 关于以下简单的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
}
如您所见,添加“-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
我使用的编译器是GCC4.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 

在快速版本中,这根本不存在;否则,两者完全相同。我假设
\uuuu mingw\uvfprintf
在这里似乎是最慢的,但我不知道它需要模拟什么样的行为使它如此慢。

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

通过GNU扩展和优化,您的
fprintf(test,“B”)
可能会被
fputc('B',test)


注意这个答案已经过时了,请参见和

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

现在,MS one明显缺少GCC one所实现的许多功能。其中一些是GNU扩展,但另一些用于C99一致性。由于您使用的是
-std=c99
,因此您请求的是一致性

但为什么这么慢?嗯,一个因素是简单性,MS版本要简单得多,所以预计它会运行得更快,即使是在很小的情况下。另一个因素是您在Windows下运行,因此预期MS版本比从Unix世界复制的版本效率更高

它是否解释了x10的系数?可能不是

你可以尝试的另一件事是:

  • fprintf()
    替换为
    sprintf()
    ,打印到内存缓冲区中而完全不接触文件。然后您可以尝试在不打印的情况下执行
    fwrite()
    。通过这种方式,您可以猜测丢失是在数据格式设置中还是在写入
    文件中

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

有关更多信息,请参阅


您可以使用
\uuu msvcrt\u fprintf()
来使用fast(不兼容)函数。

在深入研究源代码之后,我发现了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
    ;
}
\ifdef\u WIN32
#定义PFORMAT\u MINEXP\uuuupFormat\u指数\u位数()
#ifndef(两位数)指数
#定义\u获取\u输出\u格式()0
#定义两位数指数1
#恩迪夫
静态uuu内联uuu属性uuu((uu始终u内联uuu))
整数形式指数位数(空)
{
char*指数数字=getenv(“PRINTF指数数字”);
返回((指数数字!=NULL)和((无符号)(*指数数字-'0')<3))
||(_get_output_format()&两位数指数)
2.
: 3
;
}
每次我想打印时都会调用它,windows上的
getenv
一定不是很快。将该定义替换为
2
,将运行时恢复到应该的位置



因此,答案可以归结为:当使用
-std=c99
或任何符合ANSI标准的模式时,MinGW会用自己的模式切换CRT运行时。通常情况下,这不会是一个问题,但MinGW库有一个bug,它使其格式化功能的速度大大降低,超出了任何想象。

您需要将问题归结为一个完整的可实际发布的代码示例,让人们猜测你可能做了什么并不是很有成效。请注释掉printf,然后重新测试,看看这是否真的是造成差异的原因。@unwind,我道歉;我确信问题出在
printf
我打破了这个网站的基本规则。我甚至没有想到问题会出在别处。那么快速版本的汇编程序呢?这对于比较很有用。好的,所以快速版本中不存在
\u fprintf
。但是,主循环是什么样子的呢?所以一个“-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文件来检查链接器parameters@ydroneaud-是的,但是我发现
objdump-S
更容易阅读(因为asm是混合的
#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
    ;
}