找出整数的最后N位的最快方法是什么?

时间:2020-06-07 20:35:22

标签: optimization integer bit-manipulation mathematical-optimization unsigned

哪种算法最快返回无符号整数的最后n位?

1。

return num & ((1 << bits) - 1)

2。

return num % (1 << bits)

3。

let shift = num.bitWidth - bits
return (num << shift) >> shift

(其中bitWidth是整数的宽度,以位为单位)

还是有另一种更快的算法?

1 个答案:

答案 0 :(得分:3)

这将在很大程度上取决于您拥有的编译器,优化设置以及要使用的整数大小。

我在本节中的假设是答案将是“编译器将足够聪明,可以以比您选择编写的任何方法都要好的方式来优化所有这些方法。”从某种意义上说,这是正确的。考虑以下三段代码:

#include <stdint.h>
#include <limits.h>

uint32_t lastBitsOf_v1(uint32_t number, uint32_t howManyBits) {
    return number & ((1 << howManyBits) - 1);
}

uint32_t lastBitsOf_v2(uint32_t number, uint32_t howManyBits) {
    return number % (1 << howManyBits);
}

uint32_t lastBitsOf_v3(uint32_t number, uint32_t howManyBits) {
    uint32_t shift = sizeof(number) * CHAR_BIT - howManyBits;
    return (number << shift) >> shift;
}

godbolt compiler explorer处,在启用-Ofast的情况下将优化提高到-march=native,我们为以下三个函数生成了此代码:

lastBitsOf_v1(unsigned int, unsigned int):
        bzhi    eax, edi, esi
        ret

lastBitsOf_v2(unsigned int, unsigned int):
        bzhi    eax, edi, esi
        ret

lastBitsOf_v3(unsigned int, unsigned int):
        mov     eax, 32
        sub     eax, esi
        shlx    edi, edi, eax
        shrx    eax, edi, eax
        ret

请注意,编译器已识别出您正在尝试使用此函数的前两个版本,并完全重写了代码以使用bzhi x86指令。该指令将一个寄存器的低位复制到另一个寄存器中。换句话说,编译器能够生成一条汇编指令!另一方面,编译器无法识别最后一个版本要执行的操作,因此它实际上按编写的方式生成了代码,并进行了移位和减法。

但这还不是故事的结局。想象一下要提取的位数是预先知道的。例如,假设我们想要低13位。现在,看看这段代码会发生什么:

#include <stdint.h>
#include <limits.h>

uint32_t lastBitsOf_v1(uint32_t number) {
    return number & ((1 << 13) - 1);
}

uint32_t lastBitsOf_v2(uint32_t number) {
    return number % (1 << 13);
}

uint32_t lastBitsOf_v3(uint32_t number) {
    return (number << 19) >> 19;
}

这些实际上是相同的功能,只是位数硬编码。现在look at what gets generated

lastBitsOf_v1(unsigned int):
        mov     eax, edi
        and     eax, 8191
        ret
lastBitsOf_v2(unsigned int):
        mov     eax, edi
        and     eax, 8191
        ret
lastBitsOf_v3(unsigned int):
        mov     eax, edi
        and     eax, 8191
        ret

所有三个版本都被编译为完全相同的代码。编译器看到了我们在每种情况下的工作,并用更简单的代码(基本上是第一个版本)替换了它。

看到所有这些之后,您应该怎么办?我的建议如下:

  1. 除非此代码是绝对的性能瓶颈-例如,您已经测量了代码的运行时,并且绝对可以确定提取低位数字的代码实际上会使您减速-我不会完全不用为此担心。选择您能读的最易读的代码。我个人认为选项(1)最干净,但这就是我。

  2. 如果您绝对必须从中获得最大的性能,而不是信守承诺,我建议您修改一下不同版本的代码,看看每个版本中生成了什么程序集案例并进行一些性能实验。毕竟,如果这样的事情真的很重要,那么您想自己看看吧!

希望这会有所帮助!

相关问题