针对网上VC和GCC效率争论,我有想法

jackyjkchen 2009-08-28 01:05:30
到底是VC生成的exe效率高还是gcc的效率高,网上争论不休,每个人都有自己的实验论据,我以素数搜索算法做了个实验,这个算法本身不能说明什么,很简单的逻辑,不过我以不输出结果、输出结果到文件和输出结果到控制台分别实验,发现了一些规律,似乎可以解释网上为什么大家自说自话,没有统一的结论。

纯C编写的快速搜索素数算法,代码比较长,为了加速,还有素数表(2-65521),分别用VC2008和mingw 4.4.0编译
VC用release版O2优化,Mingw用release版O3优化。
搜索小于1000万的素数的结果,共664579个素数
VC:4.383s
Mingw:5.582s
VC胜出,这是IO可以几乎忽略不计的情况。

但是加上文件处理,输出结果到文件(硬盘太慢,我输出到ramdisk)
搜索小于10万的素数:
VC:0.020s
Mingw:0.018s
Mingw略快

搜索小于100万的素数:
VC:0.298s
Mingw:0.313s
这里却变成了VC略快,可能由于上限大了以后,IO开销的比例就下降了,毕竟数字越大,素数比例越低

上限改成1000万:
VC:4.935s
Mingw:5,878s
VC优势更明显了,很明显VC的优势在算法,GCC的优势在IO

为了更好的验证,我们把文件输出改成控制台输出:
小于10万:
VC:0.570s
Mingw:0.425s
Mingw优胜

小于100万:
VC:4.838s
Mingw:3.147s
Mingw优势更明显,说明在控制台IO输出上,Mingw有更大的优势,这里是用的stdio.h,我还测试了c++输出流,Mingw优势更大,VC的iostream效率低是出了名的,就不列数据了,这点大家没有争议

这样就很明显了,之所以大家会得出不同的效率测试结论,机关就在于有些人的测试程序算法多,有些人的测试程序IO多,至于linux用户对VC的不良印象,还得加上Windows(图形内核)用户控制台的低效与linux命令行(文本内核)的高速的因素,同样的硬件,分别在Windows和linux下测试VC和GCC,IO测试的差距就更大了,算法测试应该变化不大,而图形测试则是Windows优势巨大
...全文
2288 58 打赏 收藏 转发到动态 举报
写回复
用AI写文章
58 条回复
切换为时间正序
请发表友善的回复…
发表回复
c446776351 2012-04-22
  • 打赏
  • 举报
回复
貌似GCC运行速度要快多了
jerry_agle 2010-11-06
  • 打赏
  • 举报
回复
牛人多啊.... mark.... 学习学习....
  • 打赏
  • 举报
回复
xmrforever 2009-09-02
  • 打赏
  • 举报
回复
学习学习
van_lin 2009-09-02
  • 打赏
  • 举报
回复
VC 是微软的,, 你在微软的OS上 ,, 这就不公平了。。


gcc只是移植到 windows 上,, 效率肯定比不上移植前。
maxwell-l 2009-09-01
  • 打赏
  • 举报
回复
学习了 初学c++ 看还是用VC的比较多
y_3llng 2009-08-30
  • 打赏
  • 举报
回复
围观...

--!
zenny_chen 2009-08-30
  • 打赏
  • 举报
回复
从某种角度来说一个好的编译器不仅能够做出非常好的优化,并且也能一定程度上听从编程人员的指示行事。
从上两个例子来看,GCC4.0和VC2008分别在有些方面没有做得足够好。比如GCC4.0在对模板进行展开时没有做到充分展开,后续3步操作居然使用了非常耗时的call和jmp,并且也一定程度上增大了代码体积,因为我这个MemCopy函数就调用了一处。而VC2008在指令生成上不能服从,使用了非常多余的操作。

所以目前为止只有Intel C++ Compiler 10.0或以上版本在这两个方面都做得非常好。我在Mac OS X上的评估版用得非常舒服,准备花上4700多元买一套正版的,内含Intel Math库,TBB等内容。应该说还是挺不错的。
zenny_chen 2009-08-30
  • 打赏
  • 举报
回复
[Quote=引用 49 楼 jackyjkchen 的回复:]
现在的版本是11还是12?11我搞到了一部破解的,很好用,开SSE3可以比VC快20%(处理密码算法),现在网上流传的大部分还都是9和10呢
[/Quote]
我上个月从Intel官方下载的是11.0的。刚刚去了Intel官网已经看到11.1了。应该说是最新的吧,呵呵。
我大概明年,或最晚到C++0x正式发布后的第二年准备正式入手一套。
fallening 2009-08-30
  • 打赏
  • 举报
回复
[Quote=引用 48 楼 jackyjkchen 的回复:]
引用 46 楼 fallening 的回复:
gcc编译的时候,你的选项是什么?
具体的环境如何?

03级优化,和速度关系最大的不就是这个了么,其它选项对速度的影响几乎在测不出的程度( <1ms),很多linux发行软件为了加快编译速度都只用了o2,不过我这个小程序o2和o3基本看不呼出差别。

VC是o2优化,VC的极限优化Ox并不是光针对速度的,效率和o2差不多,不过和GCC不同的是,VC的debug(不优化)版本极慢,GCC不开优化时比VC快
[/Quote]

影响很大的,根据我的数值计算经验,同样一台机器,使用64位的系统比32位的要快一倍


jackyjkchen 2009-08-30
  • 打赏
  • 举报
回复
[Quote=引用 43 楼 zenny_chen 的回复:]
从某种角度来说一个好的编译器不仅能够做出非常好的优化,并且也能一定程度上听从编程人员的指示行事。
从上两个例子来看,GCC4.0和VC2008分别在有些方面没有做得足够好。比如GCC4.0在对模板进行展开时没有做到充分展开,后续3步操作居然使用了非常耗时的call和jmp,并且也一定程度上增大了代码体积,因为我这个MemCopy函数就调用了一处。而VC2008在指令生成上不能服从,使用了非常多余的操作。

所以目前为止只有Intel C++ Compiler 10.0或以上版本在这两个方面都做得非常好。我在Mac OS X上的评估版用得非常舒服,准备花上4700多元买一套正版的,内含Intel Math库,TBB等内容。应该说还是挺不错的。

[/Quote]

现在的版本是11还是12?11我搞到了一部破解的,很好用,开SSE3可以比VC快20%(处理密码算法),现在网上流传的大部分还都是9和10呢
jackyjkchen 2009-08-30
  • 打赏
  • 举报
回复
[Quote=引用 46 楼 fallening 的回复:]
gcc编译的时候,你的选项是什么?
具体的环境如何?
[/Quote]
03级优化,和速度关系最大的不就是这个了么,其它选项对速度的影响几乎在测不出的程度(<1ms),很多linux发行软件为了加快编译速度都只用了o2,不过我这个小程序o2和o3基本看不呼出差别。

VC是o2优化,VC的极限优化Ox并不是光针对速度的,效率和o2差不多,不过和GCC不同的是,VC的debug(不优化)版本极慢,GCC不开优化时比VC快
老邓 2009-08-30
  • 打赏
  • 举报
回复
zenny_chen功力深厚,佩服!
fallening 2009-08-30
  • 打赏
  • 举报
回复
gcc编译的时候,你的选项是什么?
具体的环境如何?
lin_style 2009-08-30
  • 打赏
  • 举报
回复
又见zenny_chen大牛
zenny_chen 2009-08-29
  • 打赏
  • 举报
回复

include <intrin.h>
#include <emmintrin.h>
#include <mmintrin.h>

#include <stdio.h>

extern "C" {
#define _CRT_RAND_S
#include <stdlib.h>
}


#pragma auto_inline(on)
#pragma inline_recursion(on)
template <unsigned LEN>
inline void MemCopy(void *pDest, const void* pSrc)
{
if(LEN >= 16)
{
__m128i temp;

for(unsigned i=0; i < LEN / 16; i++)
{
temp = _mm_load_si128(&((const __m128i*)pSrc)[i]);
_mm_store_si128(&((__m128i*)pDest)[i], temp);
}

MemCopy<LEN - (LEN / 16) * 16>(
&((unsigned char*)pDest)[(LEN / 16) * 16],
&((const unsigned char*)pSrc)[(LEN / 16) * 16]
);
}
else if(LEN >= 8)
{
*(__m64*)pDest = *(const __m64*)pSrc;
_mm_empty();

MemCopy<LEN - (LEN / 8) * 8>(
&((unsigned char*)pDest)[(LEN / 8) * 8],
&((const unsigned char*)pSrc)[(LEN / 8) * 8]
);
}
else if(LEN >= 4)
{
*(unsigned*)pDest = *(const unsigned*)pSrc;

MemCopy<LEN - (LEN / 4) * 4>(
&((unsigned char*)pDest)[(LEN / 4) * 4],
&((const unsigned char*)pSrc)[(LEN / 4) * 4]
);
}
else if(LEN >= 2)
{
*(unsigned short*)pDest = *(const unsigned short*)pSrc;

MemCopy<LEN - (LEN / 2) * 2>(
&((unsigned char*)pDest)[(LEN / 2) * 2],
&((const unsigned char*)pSrc)[(LEN / 2) * 2]
);
}
else if(LEN == 1)
{
*(unsigned char*)pDest = *(const unsigned char*)pSrc;
}
}

__declspec(align(16)) unsigned char src[1024];
__declspec(align(16)) unsigned char dst[1024];


static void init(void)
{
unsigned v;
for(int i=0; i < 1024 / 4; i++)
{
rand_s(&v); // VC特有号称安全的随机数函数,可用v = rand();取代
((unsigned*)src)[i] = v;
((unsigned*)dst)[i] = 0;
}
}

static void validate(void)
{
for(int i=0; i<1023; i++)
{
if(src[i] != dst[i])
{
printf("Error @: %d\n", i);
return;
}
}

puts("Succedded!");
}

int main(void)
{
unsigned __int64 beginTicks, endTicks;

beginTicks = __rdtsc();

for(int i=0; i<10; i++)
{
MemCopy<1023>(dst, src);
__asm nop //为了避免循环被优化
}

endTicks = __rdtsc();

validate();

printf("The number of cycles used to copy is: %I64u\n", endTicks - beginTicks);
}


呵呵,要使VC++2008将上述模板内联出来还真是够累的,编译效果还算不错。下面要贴两段汇编代码:

beginTicks = __rdtsc();
00401006 rdtsc
00401008 push ebx
00401009 push ebp
0040100A push esi
0040100B push edi
0040100C mov ebx,eax
0040100E mov ebp,edx
00401010 mov esi,0Ah

for(int i=0; i<10; i++)
{
MemCopy<1023>(dst, src);
00401015 call MemCopy<1023> (401080h)
__asm nop //为了避免循环被优化
0040101A nop
0040101B sub esi,1
0040101E jne main+15h (401015h)
}

endTicks = __rdtsc();


#pragma auto_inline(on)
#pragma inline_recursion(on)
template <unsigned LEN>
inline void MemCopy(void *pDest, const void* pSrc)
{
if(LEN >= 16)
{
__m128i temp;

for(unsigned i=0; i < LEN / 16; i++)
00401080 xor eax,eax
00401082 jmp MemCopy<1023>+10h (401090h)
00401084 lea esp,[esp]
0040108B jmp MemCopy<1023>+10h (401090h)
0040108D lea ecx,[ecx]
{
temp = _mm_load_si128(&((const __m128i*)pSrc)[i]);
00401090 movdqa xmm0,xmmword ptr src (40E6F0h)[eax]
_mm_store_si128(&((__m128i*)pDest)[i], temp);
00401098 movdqa xmmword ptr dst (40E2F0h)[eax],xmm0
004010A0 add eax,10h
004010A3 cmp eax,3F0h
004010A8 jb MemCopy<1023>+10h (401090h)
}

MemCopy<LEN - (LEN / 16) * 16>(
&((unsigned char*)pDest)[(LEN / 16) * 16],
&((const unsigned char*)pSrc)[(LEN / 16) * 16]
);
004010AA movq mm0,mmword ptr [src+3F0h (40EAE0h)]
004010B1 movq mmword ptr [dst+3F0h (40E6E0h)],mm0
004010B8 emms
004010BA mov eax,dword ptr [src+3F8h (40EAE8h)]
004010BF mov cx,word ptr [src+3FCh (40EAECh)]
004010C6 mov dl,byte ptr [src+3FEh (40EAEEh)]
004010CC mov dword ptr [dst+3F8h (40E6E8h)],eax
004010D1 mov word ptr [dst+3FCh (40E6ECh)],cx
004010D8 mov byte ptr [dst+3FEh (40E6EEh)],dl

从生成的代码来看两者差不多。不过我后来发现 GCC4.0 在进行剩余 7 字节拷贝操作时居然用了 擦理论,而且对于每种长度拷贝都有单独的实体,然后需要执行 jmp,这点有些匪夷所思。
不过微软的内联汇编机制始终是硬伤,在做PINSRW时,始终无法采取源操作数为存储器访问的方式,而偏偏会先将存储器中的值拷贝到一个XMM或MMX寄存器再进行计算。这一点还是Intel C++ Compiler做得好。
而GCC有非常灵活的内联汇编机制,可以用指示符来指定操作数的访问方式。
下面我们将会测一下这方面的东西。
herman~~ 2009-08-29
  • 打赏
  • 举报
回复
呵呵 mark
zenny_chen 2009-08-29
  • 打赏
  • 举报
回复
呵呵,楼主能不能把代码贴出来一下,真够神奇的。

下面我提供了一段代码来测试编译器的水准,下面代码适合于GCC,我是在Mac Mini——基于Intel Core2 T8100,45nm Enhanced Core Microarchitecture下使用GCC4.0测试的,结果,即编译出来的代码也确实是我想要的:

#include <stdio.h>
#include <stdlib.h>

#define SSE_MOVDQA_store(memDst, xmmSrc) __asm__("movdqa %%" #xmmSrc ", %0" : "=m"(memDst))
#define SSE_MOVDQA_load(xmmDst, memSrc) __asm__("movdqa %0, %%" #xmmDst : "=m"(memSrc))
#define MMX_MOVQ_store(memDst, mmxSrc) __asm__("movq %%" #mmxSrc ", %0" : "=m"(memDst))
#define MMX_MOVQ_load(mmxDst, memSrc) __asm__("movq %0, %%" #mmxDst : "=m"(memSrc))
#define MMX_EMMS() __asm__("emms")
#define INTRIN_RDTSC(tick) __asm__("rdtsc" : "=A"(tick))

typedef int __attribute__ ((vector_size (16))) __m128i;
typedef int __attribute__ ((vector_size (8))) __m64;

template <unsigned LEN>
inline void MemCopy(void *pDest, const void* pSrc)
{
if(LEN >= 16)
{
for(unsigned i=0; i < LEN / 16; i++)
{
SSE_MOVDQA_load(xmm0, ((__m128i*)pSrc)[i]);
SSE_MOVDQA_store(((__m128i*)pDest)[i], xmm0);
}

MemCopy<LEN - (LEN / 16) * 16>(
&((unsigned char*)pDest)[(LEN / 16) * 16],
&((const unsigned char*)pSrc)[(LEN / 16) * 16]
);
}
else if(LEN >= 8)
{
MMX_MOVQ_load(mm0, *(__m64*)pSrc);
MMX_MOVQ_store(*(__m64*)pDest, mm0);
MMX_EMMS();

MemCopy<LEN - (LEN / 8) * 8>(
&((unsigned char*)pDest)[(LEN / 8) * 8],
&((const unsigned char*)pSrc)[(LEN / 8) * 8]
);
}
else if(LEN >= 4)
{
*(unsigned*)pDest = *(const unsigned*)pSrc;

MemCopy<LEN - (LEN / 4) * 4>(
&((unsigned char*)pDest)[(LEN / 4) * 4],
&((const unsigned char*)pSrc)[(LEN / 4) * 4]
);
}
else if(LEN >= 2)
{
*(unsigned short*)pDest = *(const unsigned short*)pSrc;

MemCopy<LEN - (LEN / 2) * 2>(
&((unsigned char*)pDest)[(LEN / 2) * 2],
&((const unsigned char*)pSrc)[(LEN / 2) * 2]
);
}
else if(LEN == 1)
{
*(unsigned char*)pDest = *(const unsigned char*)pSrc;
}
}

unsigned char __attribute__ ((aligned (16))) src[1024];
unsigned char __attribute__ ((aligned (16))) dst[1024];


static void init(void)
{
unsigned v;
for(int i=0; i < 1024 / 4; i++)
{
v = rand();
((unsigned*)src)[i] = v;
((unsigned*)dst)[i] = 0;
}
}

static void validate(void)
{
for(int i=0; i<1023; i++)
{
if(src[i] != dst[i])
{
printf("Error @: %d\n", i);
return;
}
}

puts("Succedded!");
}

int main(void)
{
unsigned long long beginTicks, endTicks;

INTRIN_RDTSC(beginTicks);

for(int i=0; i<10; i++)
{
MemCopy<1023>(dst, src);
__asm__ volatile("mov %eax, %eax"); //为了避免循环被优化
}

INTRIN_RDTSC(endTicks);

validate();

printf("The number of cycles used to copy is: %u\n", (unsigned)(endTicks - beginTicks));
}

编译出来的结果很明显,是经过内联展开的,所以速度非常快。

下面贴出汇编:
0x00001d90 <main+0>: push ebp
0x00001d91 <main+1>: mov ebp,esp
0x00001d93 <main+3>: push esi
0x00001d94 <main+4>: push ebx
0x00001d95 <main+5>: xor ebx,ebx
0x00001d97 <main+7>: sub esp,0x20
0x00001d9a <main+10>: rdtsc
0x00001d9c <main+12>: mov DWORD PTR [ebp-0x10],eax
0x00001d9f <main+15>: mov DWORD PTR [ebp-0xc],edx
0x00001da2 <main+18>: xor eax,eax
0x00001da4 <main+20>: nop WORD PTR [eax+eax+0x0]
0x00001daa <main+26>: nop WORD PTR [eax+eax+0x0]
0x00001db0 <main+32>: movdqa xmm0,XMMWORD PTR [eax+0x2430]
0x00001db8 <main+40>: movdqa XMMWORD PTR [eax+0x2030],xmm0
0x00001dc0 <main+48>: add eax,0x10
0x00001dc3 <main+51>: cmp eax,0x3f0
0x00001dc8 <main+56>: jne 0x1db0 <main+32>
0x00001dca <main+58>: movq mm0,QWORD PTR ds:0x2820
0x00001dd1 <main+65>: movq QWORD PTR ds:0x2420,mm0
0x00001dd8 <main+72>: emms
0x00001dda <main+74>: mov DWORD PTR [esp+0x4],0x2828
0x00001de2 <main+82>: mov DWORD PTR [esp],0x2428
0x00001de9 <main+89>: call 0x1ea0 <_Z7MemCopyILj7EEvPvPKv>
0x00001dee <main+94>: mov eax,eax
0x00001df0 <main+96>: inc ebx
0x00001df1 <main+97>: cmp ebx,0xa
0x00001df4 <main+100>: jne 0x1da2 <main+18>
0x00001df6 <main+102>: rdtsc
0x00001df8 <main+104>: xor edx,edx
0x00001dfa <main+106>: mov ebx,eax
0x00001dfc <main+108>: jmp 0x1e07 <main+119>
0x00001dfe <main+110>: inc edx
0x00001dff <main+111>: cmp edx,0x3ff
0x00001e05 <main+117>: je 0x1e44 <main+180>
0x00001e07 <main+119>: movzx eax,BYTE PTR [edx+0x2430]
0x00001e0e <main+126>: cmp al,BYTE PTR [edx+0x2030]
0x00001e14 <main+132>: je 0x1dfe <main+110>
0x00001e16 <main+134>: mov DWORD PTR [esp+0x4],edx
0x00001e1a <main+138>: mov DWORD PTR [esp],0x1ee4
0x00001e21 <main+145>: call 0x3045 <dyld_stub_printf>
0x00001e26 <main+150>: mov eax,ebx
0x00001e28 <main+152>: sub eax,DWORD PTR [ebp-0x10]
0x00001e2b <main+155>: mov DWORD PTR [esp],0x1efc
0x00001e32 <main+162>: mov DWORD PTR [esp+0x4],eax
0x00001e36 <main+166>: call 0x3045 <dyld_stub_printf>
0x00001e3b <main+171>: add esp,0x20
0x00001e3e <main+174>: xor eax,eax
0x00001e40 <main+176>: pop ebx
0x00001e41 <main+177>: pop esi
0x00001e42 <main+178>: leave
0x00001e43 <main+179>: ret

稍候我会再加上注解。
我现在还在Mac OS X Leopard上,我等会儿换到Pentium4 Windows XP SP3上再把结果贴出来。
由于这段代码与操作系统无关,而与处理器相关的也就是支持到SSE2就行了,即使在AMD上也能跑。因此后面主要就是看编译器能否将这个模板元进行有效地展开了。
theone11 2009-08-29
  • 打赏
  • 举报
回复
[Quote=引用 40 楼 zenny_chen 的回复:]
所以说如果要比的话就要比纯粹的编译能力,而不能牵涉到任何与系统有关的东西。
其实还有很多可测试的方法。比如用纯C/C++写一个点积运算,看看哪个编译器能识别出乘加计算,使用PMADDWD。不过需要打开支持SSE2这个编译选项。这个选项在VC2008和GCC4.0中都有。
[/Quote]
GCC不是专注于PC平台的,编译器会根据具体CPU平台来对指令进行优化,你用gcc和vc比的话那也就仅限于x86之类的PC指令集的编译优化而已.
zenny_chen 2009-08-29
  • 打赏
  • 举报
回复
从上面的测试可以得出,GCC显得更“听话”一些。如果不是GCC的built-in functions特别难用的话也不会写宏写得那么辛苦了。由于VC的内联汇编方式非常死板,所以我宁可用intrinsic function也不会去用内联汇编。不过VC 的intrinsic function做得非常丰富,其种类甚至超过了ICC,呵呵。

[Quote=引用 38 楼 theone11 的回复:]
两个不同平台的东西,有什么可比较的?Mingw这种移植版的东西能代表gcc么?vc编译的东西,不用wine之类的模拟器能在linux下运行?
[/Quote]

所以说如果要比的话就要比纯粹的编译能力,而不能牵涉到任何与系统有关的东西。
其实还有很多可测试的方法。比如用纯C/C++写一个点积运算,看看哪个编译器能识别出乘加计算,使用PMADDWD。不过需要打开支持SSE2这个编译选项。这个选项在VC2008和GCC4.0中都有。
加载更多回复(37)

24,854

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 工具平台和程序库
社区管理员
  • 工具平台和程序库社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧