34

C++ 编译器是否将乘以 2 操作优化x*2为位移操作x<<1

我愿意相信是的。

4

13 回答 13

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)上的优化设置

于 2008-10-24T20:10:53.980 回答
24

Raymond Chen 的这篇文章可能很有趣:

x/2 什么时候不同于 x>>1?:http: //blogs.msdn.com/oldnewthing/archive/2005/05/27/422551.aspx

引用雷蒙德:

当然,编译器可以自由地识别这一点并重写您的乘法或移位操作。事实上,它很有可能这样做,因为 x+x 比乘法或移位更容易配对。您的移位或乘以 2 可能会被重写为更接近 add eax, eax 指令的东西。

[...]

即使您假设移位用符号位填充,如果 x 为负,移位和除法的结果也不同。

(-1) / 2 ≡ 0
(-1) >> 1 ≡ -1

[...]

故事的寓意是写下你的意思。如果你想除以二,那么写“/2”,而不是“>>1”。

我们只能假设告诉编译器你想要什么是明智的,而不是你想让他做什么:编译器比人类更擅长优化小规模代码(感谢 Daemin 指出这个微妙的点):如果你真的想要优化,使用分析器,并研究算法的效率。

于 2008-10-24T21:16:55.797 回答
12

VS 2008 将矿井优化为 x << 1。

    x = x * 2;
004013E7  mov         eax,dword ptr [x] 
004013EA  shl         eax,1 
004013EC  mov         dword ptr [x],eax 

编辑:这是使用禁用优化 (/Od) 的 VS 默认“调试”配置。使用任何优化开关(/O1、/O2(VS“零售”)或/Ox)都会导致添加自我代码 Rob 发布。此外,为了更好地衡量,我验证确实与/Od 和 /Ox 中的 cl 编译器的x = x << 1处理方式相同。x = x * 2因此,总而言之,用于 x86 的 cl.exe 版本 15.00.30729.01 处理方式* 2相同<< 1,我希望几乎所有其他最近的编译器都这样做。

于 2008-10-24T20:14:54.237 回答
11

如果 x 是浮点数,则不会。

于 2008-10-24T20:03:45.180 回答
5

是的。他们还优化了其他类似的操作,例如乘以 2 的非幂,可以重写为一些移位的总和。他们还将优化除以 2 的幂为右移,但请注意,在处理有符号整数时,这两个操作是不同的!编译器必须发出一些额外的位旋转指令以确保正数和负数的结果相同,但它仍然比除法更快。它也同样通过 2 的幂来优化模数。

于 2008-10-24T20:05:39.777 回答
5

答案是“如果它更快”(或更小)。这在很大程度上取决于目标架构以及给定编译器的寄存器使用模型。一般来说,答案是“是的,总是”,因为这是一个非常简单的窥视孔优化实现,通常是一个不错的胜利。

于 2008-10-24T20:39:48.883 回答
4

这只是优化器可以做的事情的开始。要查看您的编译器做了什么,请查找导致它发出汇编源代码的开关。对于 Digital Mars 编译器,可以使用 OBJ2ASM 工具检查输出汇编器。如果您想了解编译器的工作原理,查看汇编器输出可能会很有启发性。

于 2008-11-04T06:34:16.107 回答
1

我敢肯定他们都做了这些优化,但我想知道它们是否仍然相关。较旧的处理器通过移位和加法进行乘法运算,这可能需要多个周期才能完成。另一方面,现代处理器具有一组桶形移位器,它们可以在一个时钟周期或更短的时间内同时完成所有必要的移位和加法。有没有人真正对这些优化是否真的有帮助进行基准测试?

于 2008-10-24T21:22:51.730 回答
0

是他们会。

于 2008-10-24T20:05:29.777 回答
0

除非在语言标准中指定了某些内容,否则您永远不会得到此类问题的有保证的答案。如有疑问,请让您的编译器吐出汇编代码并检查。这将是真正了解的唯一方法。

于 2008-10-24T20:44:51.040 回答
0

@费鲁乔巴列塔

这是个好问题。我去谷歌搜索试图找到答案。

我无法直接找到英特尔处理器的答案,但这个页面有人试图计时。它显示转变的速度是广告和倍增的两倍多。位移非常简单(乘法可以是位移和加法),这是有道理的。

于是我用谷歌搜索了 AMD,发现了一份 2002 年的 Athlon 优化指南,其中列出了将数字乘以 2 到 32 之间的常数的最快方法。有趣的是,这取决于数字。有些是广告,有些是轮班。在第 122 页

Athlon 64的指南显示了相同的内容(第 164 页左右)。它说乘法是 3(32 位)或 4(64 位)循环操作,其中移位为 1,加法为 2。

看来它作为优化仍然有用。

但是,忽略循环计数,这种方法会阻止您占用乘法执行单元(可能),所以如果您在一个紧密的循环中进行大量乘法运算,其中一些使用常量而一些不使用额外的调度空间可能是有用。

但那是猜测。

于 2008-10-25T00:43:42.103 回答
0

为什么你认为这是一种优化?

为什么不2*xx+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*2x<<1产生更快的二进制文件。优化编译器将尝试为给定的程序生成最快的代码,而不是为孤立的操作生成。因此,同一代码的优化结果可能会因周围的代码而有很大差异,而且它们可能根本不是微不足道的。

通常,很少有基准测试显示编译器之间存在很大差异。因此,一个公平的假设是编译器做得很好,因为如果还剩下便宜的优化,至少有一个编译器会实现它们——所有其他编译器都会在下一个版本中跟进。

于 2018-09-12T07:45:01.117 回答
-9

这取决于你有什么编译器。例如,Visual C++ 在优化方面是出了名的差。如果您编辑帖子以说明您使用的是什么编译器,那么回答起来会更容易。

于 2008-10-24T20:32:20.893 回答