我想知道按 2 的幂移动时执行逻辑右移是否更快
例如,是
myUnsigned >> 4
任何快于
myUnsigned >> 3
我很感激每个人的第一反应是告诉我,人们不应该担心这样的小事情,它使用正确的算法和集合来减少数量级的重要性。我完全同意你的观点,但我真的想尽我所能从嵌入式芯片(ATMega328)中挤出来——我刚刚获得了值得“哇哦!”的性能转变。通过用位移来代替除法,所以我向你保证,这确实很重要。
我想知道按 2 的幂移动时执行逻辑右移是否更快
例如,是
myUnsigned >> 4
任何快于
myUnsigned >> 3
我很感激每个人的第一反应是告诉我,人们不应该担心这样的小事情,它使用正确的算法和集合来减少数量级的重要性。我完全同意你的观点,但我真的想尽我所能从嵌入式芯片(ATMega328)中挤出来——我刚刚获得了值得“哇哦!”的性能转变。通过用位移来代替除法,所以我向你保证,这确实很重要。
让我们看一下数据表:
http://atmel.com/dyn/resources/prod_documents/8271S.pdf
据我所知,ASR(算术右移)总是移位一位,不能取移位的位数;执行需要一个周期。因此,右移 n 位需要 n 个周期。2 的幂的行为与任何其他数字相同。
在AVR 指令集中,算术右移和左移一次发生一位。所以,对于这个特定的微控制器,shifting>> n
意味着编译器实际上做了 n 个单独的asr
操作,我猜>>3
是一个比>>4
.
顺便说一句,这使得 AVR 相当不寻常。
您必须查阅处理器的文档以获取此信息。即使对于给定的指令集,也可能存在不同的成本,具体取决于模型。例如,在一个非常小的处理器上,可以想象移位一个比其他值更快(在某些 IA32 处理器上旋转指令就是这种情况,但这只是因为编译器很少生成该指令)。
根据http://atmel.com/dyn/resources/prod_documents/8271S.pdf,ATMega328的所有逻辑移位都在一个周期内完成。但是,当然,正如评论中所指出的,所有逻辑移位都是一位。因此,移位的成本n
是指令n
周期。n
事实上,ATMega 没有像大多数(如果不是全部)其他 8 位 MCU 那样的桶式移位器。因此,它每次只能移动 1,而不是像更强大的 CPU 那样的任意值。因此,从理论上讲,移位 4比移位 3 慢
然而 ATMega确实有一个交换半字节指令,所以实际上x >> 4
比x >> 3
假设x
一个uint8_t
thenx >>= 3
由3 次右移实现
x >>= 1;
x >>= 1;
x >>= 1;
而x >>= 4
只需要交换并有点清楚
swap(x); // swap the top and bottom nibbles AB <-> BA
x &= 0x0f;
或者
x &= 0xf0;
swap(x);
对于更大的跨寄存器移位,还有各种优化方法
使用由低部分和高部分组成的uint16_t
变量,则很简单y
y0
y1
y >> 8
y0 = y1;
y1 = 0;
同样y >> 9
可以优化为
y0 = y1 >> 1;
y1 = 0;
因此甚至比在 char 上移动 3 还要快
总之,移位时间根据移位距离而变化,但对于较长或非 2 次幂值,它不一定会变慢。通常,在 8 位字符内移动最多需要 3 条指令
右移 4 是通过上面的 aswap
和and
类似实现的
swap r24
andi r24,lo8(15)
右移 3 必须用 3 条指令完成
lsr r24
lsr r24
lsr r24
左移也以相同的方式优化
这取决于处理器的构建方式。如果处理器具有桶式旋转,它可以在一次操作中移动任意数量的位,但这会占用芯片空间和功率预算。最经济的硬件只能向右旋转一个,并带有关于环绕钻头的选项。接下来是一个可以向左或向右旋转一个的。我可以想象一个具有 1-shifter、2-shifter、4-shifter 等的结构,在这种情况下,4 可能比 3 快。
先反汇编,然后对代码计时。不要因为别人告诉你而气馁,你在浪费时间。您获得的知识将使您成为扑灭大公司火灾的首选人。在这个行业中,真正了解幕后知识的人数正在以惊人的速度下降。
听起来像其他人在这里解释了真正的答案,反汇编会显示单个位移指令。因此,4 个班次将花费 3 个班次所花费时间的 133%,或者 3 个班次是 4 个班次所花费时间的 75%,具体取决于您如何比较数字。你的测量应该反映这种差异,如果他们不这样做,我会继续这个实验,直到你完全理解执行时间。
如果您的目标处理器有移位指令(这很可能),那么它取决于该指令的硬件实现是否在移位 2 位的幂或移位其他数字之间存在任何差异。但是,这不太可能有所作为。
恕我直言,在开始测量之前,您甚至不应该开始谈论性能。用除法编译你的程序。跑。测量时间。换档重复。
用位移替换除法
这与负数不同:
char div2 (void)
{
return (-1) / 2;
// ldi r24,0
}
char asr1 (void)
{
return (-1) >> 1;
// ldi r24,-1
}