[英]Left-shift by (64 > shift > 32) bits on UINT64 in VS shifts only 32 bits
[英]How do I efficiently left-shift by N bits using single-bit shifts?
有些像 MSP430 這樣的 CPU 沒有多位移位,而只有單位移位或循環指令。 這讓我很好奇“過去”的程序員如何實現多位移位,當時他們所能做的就是一次位移一位。
我知道這樣做的“愚蠢”方式,就是這樣:
#include <cstdint>
uint64_t lshift(uint64_t x, uint64_t shift) {
for (uint64_t i = 0; i < shift; ++i) {
x <<= 1;
}
}
有沒有什么方法可以做到不具有 O(n) 復雜度? 或者是否至少有一個實現可以讓我在編譯時知道移位,這通常是移位的情況?
我的直覺是x << (1 << (1 << 1))
與x << 4
相同,所以也許可以通過組合這樣的移位將其減少到 O(log n) 。
我的直覺是錯誤的,但其他操作也可以產生類似的效果。 x << 1
等價於x += x
所以x += x, x += x, x += x
等價於x << 4
。 乘以 2 的冪也可以。
注意:這里使用 C++ 只是為了方便起見,我知道總會有一個左移運算符。 我只是不想在 MP430 組件中考慮這個。
有關以下代碼的背景信息,請在 Internet 上搜索“Duff's Device”。
您可以使用帶有 fall through 的switch
語句:
uint32_t Shift_Value(uint32_t value, unsigned int shift_quantity)
{
switch (shift_quantity)
{
case 31:
value <<= 1;
case 30:
value <<= 1;
case 29:
value <<= 1;
// ...
case 1:
value <<= 1;
}
return value;
}
上面的代碼很有趣,因為它是一個跳轉到移位操作數組的表。 可以將其與展開for
循環進行比較,但它的優點是執行跳轉到“展開”的適當位置。
我以前在嵌入式系統中使用過這種模式來提高性能。
我建議打印出編譯器生成的匯編語言並學習匯編語言。 :-)
此外,優化可能是 O(1),因為沒有循環,只有計算和跳轉。
如果你有一個乘數,那么
uint32_t multipliers[] = {1,2,4,8,16 ...};
uint32_t shift(uint32_t x, uint32_t shift)
{
return x * multipliers[shift];
}
TL;DR:事實上,正如您想象的那樣,通過多個步驟進行輪班通常會通過多個輪班來完成。 但是可以使用一些技巧來避免移動太多次。 例如,一些算法被設計成只需要移位 1,或者如果需要更大的移位,那么可以使用 ISA 中的一些特殊的按位指令進行優化
為嵌入式系統編程需要更深入地了解該架構,以實現良好的性能和 RAM/ROM 使用。 例如,選擇變量以使其適合機器字。 除非絕對必要,否則沒有人會像在 16 位或 8 位 MCU 上那樣使用uint64_t
。 對多字變量(包括位移)的操作需要更多的指令,因此它們通常不會被內聯。 通常,對於字大小倍數的移位與移位數組元素類似,因此它們會非常快
任意變量移位需要一個桶形移位器,它比簡單的移位寄存器占用更多的芯片空間,因此大多數嵌入式架構一次只能移位一位。 它們中的大多數還包括某種交換半字指令以克服限制並允許足夠快的大距離移位。 例如,像 8051、PIC 或 AVR 這樣的 8 位微控制器具有交換半字節指令。 看:
MSP430 是一個 16 位 MCU,所以它有一個SWPB
指令來交換字節,它可以類似地用於快速移位 8。以下是Clang 生成的一些示例(我的評論,注意移位 8 和移位更大完成了 8 個以上):
shift_left_15(unsigned short): ; @shift_left_15(unsigned short)
mov.b r12, r12
swpb r12 ; swap bytes then shift left 7 times
add r12, r12
add r12, r12
add r12, r12
add r12, r12
add r12, r12
add r12, r12
add r12, r12
ret
shift_left_12(unsigned short): ; @shift_left_12(unsigned short)
mov.b r12, r12
swpb r12 ; swap bytes then shift left 4 times
add r12, r12
add r12, r12
add r12, r12
add r12, r12
ret
shift_left_10(unsigned short): ; @shift_left_10(unsigned short)
mov.b r12, r12
swpb r12 ; swap bytes then shift left 2 times
add r12, r12
add r12, r12
ret
shift_left_9(unsigned short): ; @shift_left_9(unsigned short)
mov.b r12, r12
swpb r12
add r12, r12
ret
shift_left_8(unsigned short): ; @shift_left_8(unsigned short)
mov.b r12, r12
swpb r12 ; just swap bytes
ret
shift_left_7(unsigned short): ; @shift_left_7(unsigned short)
add r12, r12
add r12, r12
add r12, r12
add r12, r12
add r12, r12
add r12, r12
add r12, r12
ret
shift_left_3(unsigned short): ; @shift_left_3(unsigned short)
add r12, r12
add r12, r12
add r12, r12
ret
您可以打開上面的 Godbolt 鏈接以獲取完整輸出
如果您使用的是 MSP430X,那么它能夠從 1 位位置轉換為 4 位位置,這極大地簡化了轉換過程
shift_left_15(unsigned short):
PUSHM.W #1, R4
MOV.W R1, R4
rpt #15 { rlax.w R12
POPM.W #1, r4
RET
shift_left_12(unsigned short):
PUSHM.W #1, R4
MOV.W R1, R4
rpt #12 { rlax.w R12
POPM.W #1, r4
RET
shift_left_10(unsigned short):
PUSHM.W #1, R4
MOV.W R1, R4
rpt #10 { rlax.w R12
POPM.W #1, r4
RET
shift_left_9(unsigned short):
PUSHM.W #1, R4
MOV.W R1, R4
rpt #9 { rlax.w R12
POPM.W #1, r4
RET
shift_left_8(unsigned short):
PUSHM.W #1, R4
MOV.W R1, R4
rpt #8 { rlax.w R12
POPM.W #1, r4
RET
shift_left_7(unsigned short):
PUSHM.W #1, R4
MOV.W R1, R4
rpt #7 { rlax.w R12
POPM.W #1, r4
RET
shift_left_3(unsigned short):
PUSHM.W #1, R4
MOV.W R1, R4
rpt #3 { rlax.w R12
POPM.W #1, r4
RET
右移可以用同樣的方式完成,但將add
替換為rrc/rra
rlax
並將rlax
為rrax
。 在 Godbolt 上查看演示
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.