没有“-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 速度差异的主要内容,如果未能解决你的问题,请参考以下文章
安装Libsvm工具箱出现问题E:\MATLAB7\BIN\WIN32\MEX.PL: Error: 'CFLAGS=\$CFLAGS -std=c99
报错storage size of ‘act’ isn’t known当使用std=c99编译struct sigaction