C ++编译器是否优化了两个操作x*2
乘以比特移位操作x<<1
?
我很乐意相信是的。
答案 0 :(得分:31)
实际上VS2008将其优化为x + x:
01391000 push ecx
int x = 0;
scanf("%d", &x);
01391001 lea eax,[esp]
01391004 push eax
01391005 push offset string "%d" (13920F4h)
0139100A mov dword ptr [esp+8],0
01391012 call dword ptr [__imp__scanf (13920A4h)]
int y = x * 2;
01391018 mov ecx,dword ptr [esp+8]
0139101C lea edx,[ecx+ecx]
在x64版本中,它更加明确并使用:
int y = x * 2;
000000013FB9101E mov edx,dword ptr [x]
printf("%d", y);
000000013FB91022 lea rcx,[string "%d" (13FB921B0h)]
000000013FB91029 add edx,edx
这将是'最大化速度'(/ O2)
的优化设置答案 1 :(得分:22)
Raymond Chen的这篇文章可能很有趣:
当x / 2与x&gt;> 1时不同?: http://blogs.msdn.com/oldnewthing/archive/2005/05/27/422551.aspx
引用雷蒙德:
当然,编译器可以自由识别并重写乘法或移位操作。事实上,它很可能会这样做,因为x + x比乘法或移位更容易配对。您的移位或乘以二可能会被重写为更接近添加eax,eax指令的内容。
[...]
即使您假设移位填充符号位,如果x为负,则移位和除法的结果也不同。
( - 1)/2≡0
(-1)&gt;&gt; 1≡-1[...]
故事的寓意是写下你的意思。如果你想除以2,则写“/ 2”,而不是“&gt;&gt; 1”。
我们只能假设告诉编译器你想要什么,而不是你想要他做什么是明智的:编译器优于人类优化小规模代码(感谢Daemin)指出这个微妙的观点):如果你真的想要优化,请使用分析器,研究算法的效率。
答案 2 :(得分:12)
VS 2008优化了我的x&lt;&lt; 1。
x = x * 2;
004013E7 mov eax,dword ptr [x]
004013EA shl eax,1
004013EC mov dword ptr [x],eax
编辑:这是使用VS默认的“调试”配置,禁用优化(/ Od)。使用任何优化开关(/ O1,/ O2(VS“零售”)或/ Ox)会导致添加自我代码Rob发布。另外,为了更好地衡量,我验证{/ 1}}确实与/ Od和/ Ox中的cl编译器一样对待x = x << 1
。因此,总而言之,x86的cl.exe版本15.00.30729.01同样对待x = x * 2
和* 2
,我希望几乎所有其他最近的编译器都这样做。
答案 3 :(得分:11)
如果x不是浮点数,则不会。
答案 4 :(得分:5)
是。它们还优化了其他类似的操作,例如乘以2的非幂,可以将其重写为某些移位的总和。他们还将优化2的幂划分为右移,但要注意当使用有符号整数时,这两个操作是不同的!编译器必须发出一些额外的位错误指令,以确保正数和负数的结果相同,但它仍然比进行除法更快。它也同样用2的幂来优化模数。
答案 5 :(得分:5)
答案是“如果它更快”(或更小)。这很大程度上取决于目标体系结构以及给定编译器的寄存器使用模型。一般来说,答案是“是的,永远”,因为这是一个非常简单的窥视孔优化,通常是一个不错的胜利。
答案 6 :(得分:4)
这只是优化者可以做的开始。要查看编译器的功能,请查找导致其发出汇编源的开关。对于Digital Mars编译器,可以使用OBJ2ASM工具检查输出汇编器。如果您想了解编译器的工作原理,查看汇编器输出可能非常有启发性。
答案 7 :(得分:1)
我确信他们都做了这些优化,但我想知道它们是否仍然相关。较旧的处理器通过移位和添加进行乘法运算,这可能需要多个周期才能完成。另一方面,现代处理器具有一组桶形移位器,它们可以在一个或多个时钟周期内同时进行所有必要的移位和添加。有没有人真正确定这些优化是否真的有用?
答案 8 :(得分:0)
是的,他们会。
答案 9 :(得分:0)
除非在语言标准中指定了某些内容,否则您将无法获得对此类问题的保证答案。如果有疑问,您的编译器会吐出汇编代码并检查。这将是真正了解的唯一方式。
答案 10 :(得分:0)
@Ferruccio Barletta
这是一个很好的问题。我去谷歌搜索试图找到答案。
我无法直接找到英特尔处理器的答案,但是this页面上有人试图计时。它显示的转变速度是广告和倍数的两倍多。比特移位非常简单(乘法可以是移位和加法),这是有道理的。
然后我用Google搜索了AMD,并从2002年发现了一个旧的Athlon优化指南列出了最快的方法来将数字乘以2到32之间的数量。有趣的是,它取决于数量。有些是广告,有些是转变。它在page 122上。
Athlon 64的指南显示相同的内容(第164页左右)。它表示乘法是3(32位)或4(64位)循环操作,其中移位为1且加法为2。
它似乎仍然可以作为优化。
忽略循环计数,这种方法会阻止你绑定乘法执行单元(可能),所以如果你在一个紧凑的循环中进行大量的乘法,其中一些使用常量而另一些则没有额外的调度房间可能有用。
但那是猜测。
答案 11 :(得分:0)
您为什么认为这是一种优化?
为什么不2*x
→x+x
?还是乘法运算和左移运算一样快(也许只有在被乘数中只有一位设置的情况下)?如果您从不使用结果,为什么不将其从编译输出中删除呢?如果编译器已将2
加载到某个寄存器,则乘法指令可能会更快,例如如果我们必须先加载班次计数。也许移位操作更大,并且您的内部循环不再适合CPU的预取缓冲区,从而降低了性能?也许编译器可以证明,您调用函数x
的唯一时间将是值37,而x*2
可以被74
代替吗?也许您正在做2*x
,其中x
是一个循环计数(在遍历两个字节的对象时非常常见,尽管是隐式的)?然后,编译器可以从以下位置更改循环
for(int x = 0; x < count; ++x) ...2*x...
等效(忽略病理学)
int count2 = count * 2
for(int x = 0; x < count2; x += 2) ...x...
用一个乘法替换count
乘法,或者它可能能够利用lea
指令将乘法与内存读取结合起来。
我的观点是:有数百万个因素决定是否用x*2
替换x<<1
会产生更快的二进制文件。优化的编译器将尝试为给定的程序而不是孤立的操作生成最快的代码。因此,相同代码的优化结果可能会因周围的代码而有很大不同,并且可能根本不算什么。
通常,很少有基准能表明编译器之间的巨大差异。因此,可以公平地假设编译器做得很好,因为如果剩下廉价的优化,则至少有一个编译器将实现它们,而其他所有编译器将在其下一个发行版中采用。
答案 12 :(得分:-9)
这取决于您拥有的编译器。例如,Visual C ++在优化方面非常糟糕。如果您编辑帖子以说出您正在使用的编译器,那么回答会更容易。