我不想优化任何东西,我发誓,我只想出于好奇而问这个问题。
我知道在大多数硬件上都有一个比特移位的汇编命令(例如shl
,shr
),这是一个命令。但是,你转移了多少比特(纳秒级,或CPU技巧)是否重要?换句话说,在任何CPU上都是以下哪一种更快?
x << 1;
和
x << 10;
请不要因为这个问题而讨厌我。 :)
答案 0 :(得分:83)
可能取决于CPU。
然而,所有现代CPU(x86,ARM)都使用“桶形移位器” - 专门设计用于在恒定时间内执行任意移位的硬件模块。
所以底线是......不。没有区别。
答案 1 :(得分:62)
某些嵌入式处理器只有“逐个移位”指令。在此类处理器上,编译器会将x << 3
更改为((x << 1) << 1) << 1
。
我认为摩托罗拉MC68HCxx是受此限制的更受欢迎的家庭之一。幸运的是,这种架构现在非常罕见,现在大多数都包括一个具有可变移位尺寸的桶形移位器。
英特尔8051具有许多现代衍生产品,也无法移动任意数量的位。
答案 2 :(得分:28)
有很多案例。
许多高速MPU都有桶形移位器,类似多路复用器的电子电路,可以在恒定时间内进行任何移位。
如果MPU只有1位移位x << 10
通常会更慢,因为它主要通过10次移位或2次移位的字节复制完成。
但众所周知,x << 10
甚至比<{1}}更快。如果x是16位,则只需要低6位(所有其他都将被移出),因此MPU只需加载较低的字节,因此只对8位存储器进行单一访问周期,而x << 1
需要两个访问周期。如果访问周期慢于shift(并清除低位字节),x << 10
将更快。这可能适用于具有快速板载程序ROM的微控制器,同时访问慢速外部数据RAM。
作为案例3的补充,编译器可能会关注x << 10
中的有效位数,并优化对低宽度位的操作,例如将16x16乘法替换为16x8乘法(因为低位字节始终为零) )。
注意,有些微控制器根本没有左移指令,而是使用x << 10
。
答案 3 :(得分:9)
在ARM上,这可以作为另一条指令的副作用来完成。所以潜在地,它们中的任何一个都没有延迟。
答案 4 :(得分:9)
这是my favorite CPU,其中x<<2
的时间是x<<1
的两倍:)
答案 5 :(得分:7)
这取决于CPU和编译器。即使底层CPU具有桶形移位器的任意位移,这只有在编译器利用该资源时才会发生。
请记住,在C和C ++中,将数据位宽度以外的任何内容移位是“未定义的行为”。签名数据的右移也是“实现定义的”。而不是过分关注速度,关注你在不同的实现上得到相同的答案。
引自ANSI C第3.3.7节:
3.3.7按位移位运算符
语法
shift-expression: additive-expression shift-expression << additive-expression shift-expression >> additive-expression
约束
每个操作数都应具有 积分型。
语义
整体促销活动 在每个操作数上执行。 结果的类型是 提升左操作数。如果值 右操作数是否定的或是 大于或等于宽度 升级的左操作数的位, 行为未定义。
E1的结果&lt;&lt; E2是E1 左移E2位位置;腾空 位用零填充。如果E1有 无符号类型,值的 结果是E1乘以 数量,2增加到功率E2, 如果E1有,则减少模数ULONG_MAX + 1 输入unsigned long,UINT_MAX + 1 除此以外。 (常量ULONG_MAX 和UINT_MAX在标题中定义 。)
E1&gt;的结果&gt; E2是E1 右移E2位位置。如果E1 有一个无符号类型或E1有一个 签名类型和非负值, 结果的值是 E1的商的组成部分 除以数量,2提高到 权力E2。如果E1有签名 类型和负值, 结果价值是 实现定义的。
所以:
x = y << z;
“&lt;&lt;”:y×2 z (未定义,如果发生溢出);
x = y >> z;
“&gt;&gt;”:为签名定义的实现(通常是算术移位的结果:y / 2 z )。
答案 6 :(得分:7)
可以想象,在8位处理器上,对于16位值,x<<1
实际上比<{em}慢得多 。
例如x<<10
的合理翻译可能是:
x<<1
而byte1 = (byte1 << 1) | (byte2 >> 7)
byte2 = (byte2 << 1)
会更简单:
x<<10
请注意byte1 = (byte2 << 2)
byte2 = 0
如何更频繁地转变,甚至比x<<1
更远。此外,x<<10
的结果不依赖于byte1的内容。这可以进一步加快操作。
答案 7 :(得分:5)
在几代英特尔CPU上(P2或P3?但不是AMD,如果我没记错的话),比特移位操作速度非常慢。 Bitshift by 1 bit应该总是很快,因为它只能使用加法。要考虑的另一个问题是,通过恒定位数的位移是否比可变长度位移更快。即使操作码的速度相同,在x86上,位移的非常量右手操作数也必须占用CL寄存器,这会对寄存器分配造成额外的限制,并且可能会使程序慢下来。
答案 8 :(得分:3)
与往常一样,它取决于周围的代码上下文:例如你使用x<<1
作为数组索引吗?或者将其添加到其他东西?在任何一种情况下,小的移位计数(1或2)通常可以比编译器最终必须只移位时更优化。更不用说整个吞吐量与延迟与前端瓶颈之间的权衡。微小片段的表现不是一维的。
硬件转换指令不是编译器编译x<<1
的唯一选项,但其他答案主要是假设。
x << 1
完全等同于x+x
表示无符号和2的补码有符号整数。编译器总是知道他们在编译时所针对的硬件,因此他们可以利用这样的技巧。
在Intel Haswell上,add
每时钟吞吐量为4,但立即计数的shl
每时钟吞吐量仅为2。 (有关说明表以及http://agner.org/optimize/标记wiki中的其他链接,请参阅x86)。 SIMD向量移位为每时钟1个(Skylake中为2个),但SIMD向量整数加上每个时钟2个(Skylake中为3个)。但延迟是相同的:1个周期。
还有一个shl
的特殊转换编码,其中计数隐含在操作码中。 8086没有立即计数轮班,只有一个和cl
注册。这主要与右移相关,因为除非你移动内存操作数,否则你可以添加左移。但如果稍后需要该值,最好先加载到寄存器中。但无论如何,shl eax,1
或add eax,eax
比shl eax,10
短一个字节,代码大小可以直接(解码/前端瓶颈)或间接(L1I代码缓存未命中)影响性能。
更一般地说,在x86上的寻址模式下,有时可以将小移位计数优化为缩放索引。目前常用的大多数其他架构都是RISC,并且没有扩展索引寻址模式,但x86是一个通用的架构,值得一提。 (例如,如果要索引一个4字节元素的数组,则int arr[]; arr[x<<1]
)可以将比例因子增加1。
在仍然需要x
的原始值的情况下,需要复制+移位。但大多数x86整数指令就地运行。(目标是add
或shl
等指令的来源之一。)x86-64 System V调用约定通过寄存器中的args,edi
中的第一个arg和eax
中的返回值,因此返回x<<10
的函数也会使编译器发出copy + shift代码。
LEA
instruction lets you shift-and-add(移位计数为0到3,因为它使用寻址模式机器编码)。它将结果放在一个单独的寄存器中。
int shl1(int x) { return x<<1; }
lea eax, [rdi+rdi] # 1 cycle latency, 1 uop
ret
int shl2(int x) { return x<<2; }
lea eax, [4*rdi] # longer encoding: needs a disp32 of 0 because there's no base register, only scaled-index.
ret
int times5(int x) { return x * 5; }
lea eax, [rdi + 4*rdi]
ret
int shl10(int x) { return x<<10; }
mov eax, edi # 1 uop, 0 or 1 cycle latency
shl eax, 10 # 1 uop, 1 cycle latency
ret
具有2个组件的LEA在最近的Intel和AMD CPU上具有1个周期延迟和2个每时钟吞吐量。 (Sandybridge家族和Bulldozer / Ryzen)。在英特尔,lea eax, [rdi + rsi + 123]
只有1个时钟吞吐量,3c延迟。 (相关:Why is this C++ code faster than my hand-written assembly for testing the Collatz conjecture?详细介绍了这一点。)
无论如何,复制+移位10需要单独的mov
指令。在许多最近的CPU上可能是零延迟,但它仍然需要前端带宽和代码大小。 (Can x86's MOV really be "free"? Why can't I reproduce this at all?)
还相关:How to multiply a register by 37 using only 2 consecutive leal instructions in x86?。
编译器也可以自由转换周围的代码,因此没有实际的转换,或者它与其他操作结合。
例如if(x<<1) { }
可以使用and
来检查除高位之外的所有位。在x86上,您使用test
指令,例如test eax, 0x7fffffff
/ jz .false
而不是shl eax,1 / jz
。此优化适用于任何班次计数,它也适用于大计数班次较慢(如奔腾4)或不存在(某些微控制器)的计算机。
许多ISA都有位操作指令,而不仅仅是移位。例如PowerPC有很多位字段提取/插入指令。或者ARM将源操作数的移位作为任何其他指令的一部分。 (因此,移位/旋转指令只是move
的一种特殊形式,使用移位源。)
请记住, C不是汇编语言。在调整源代码以便有效编译时,请始终查看优化的编译器输出。