【问题标题】:How do I efficiently left-shift by N bits using single-bit shifts?如何使用单位移位有效地左移 N 位?
【发布时间】:2020-09-04 15:17:13
【问题描述】:

像 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 &lt;&lt; (1 &lt;&lt; (1 &lt;&lt; 1))x &lt;&lt; 4 相同,所以也许可以通过组合这样的移位将其减少到 O(log n)。

编辑

我的直觉是错误的,但其他操作可能会产生类似的效果。 x &lt;&lt; 1 等价于x += x 所以x += x, x += x, x += x 等价于x &lt;&lt; 4。乘以 2 的幂也可以。


注意:这里使用 C++ 只是为了方便,我知道总会有一个左移运算符。我只是不想在 MP430 组装中考虑这个。

【问题讨论】:

  • 如果它不支持多位移位,我很确定这是不可能的。这就像单核 CPU 上的多线程
  • 没有。如果没有 N 个单位移位,你就不能左移 N 位。
  • 如果您没有多位移位移位,这不太可能,但是您有乘法吗?
  • @Colin 如果我有一个乘法,那会改变什么吗?乘以 8 给我一个 3 位移位,但是我如何从“我必须位移 3 位”到“我有一个 8”。通常这需要 (1
  • 你想多了。 C++ 和 C 被定义为一个抽象机器,在它上面定义了移位运算符。这是如何在特定平台(如 MSP430)上完成的,是编译器的责任。您不必在 C/C++ 级别上做任何事情。您必须通过自己的代码执行此操作的唯一情况是您对未内置的类型执行此操作。

标签: c++ bit-manipulation bit bit-shift


【解决方案1】:

有关以下代码的背景信息,请在互联网上搜索“Duff 的设备”。

您可以使用 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),因为没有循环,只有计算和跳转。

【讨论】:

  • 仍然是 O(n),因为最坏的情况是 31 次,其中发生 31 次单独的班次。
【解决方案2】:

如果你有一个乘数,那么

uint32_t multipliers[] = {1,2,4,8,16 ...};
uint32_t shift(uint32_t x, uint32_t shift)
{
    return x * multipliers[shift];
}

【讨论】:

  • 这将是一条指令还是仍会归结为 n 个单位移位?
  • 这取决于你是否有硬件乘法器,以及编译器如何工作,看起来一些 MSP430 变体确实有一个乘法器作为外围设备,所以我希望一个像样的编译器会使用它而不是有限的单个位移
  • 这真的很有趣,因为这种优化通常是反向进行的
  • 只有在有非常快的乘法器时才有效(在这种嵌入式系统中不太可能)
【解决方案3】:

TL;DR: 实际上,如您所想,多步转换通常会通过多次转换来完成。但是可以使用一些技巧来避免移动太多次。例如,一些算法被设计成只需要移位 1,或者如果需要更大的移位,则可以使用 ISA 中的一些特殊的按位指令进行优化

嵌入式系统编程需要对该架构有更深入的了解,才能获得良好的性能和 RAM/ROM 使用率。例如,选择变量以使其适合机器字。除非绝对必要,否则没有人会像在 16 位或 8 位 MCU 上那样使用 uint64_t。对多字变量(包括位移)的操作需要更多的指令,因此它们通常不会被内联。一般来说,对于字长的倍数的移位与移位数组元素类似,因此它们会非常快


移动任意变量需要barrel shifter,它比简单的shift register 占用更多的裸片空间,因此大多数嵌入式架构一次只能移动一位。它们中的大多数还包括某种 swap halfword 指令来克服限制并允许足够快的大距离移位。例如 8 位微控制器(如 8051、PIC 或 AVR)具有交换半字节指令。见:

MSP430 是一个 16 位 MCU,因此它有一个 SWPB 指令来交换 字节,它可以类似地用于快速移位 8。这里是 some examples generated by Clang(我用 cmets ,注意 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 位,greatly simplifies the shifting procedure

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 替换为rrax。见demo on Godbolt

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-12-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-07
    • 1970-01-01
    相关资源
    最近更新 更多