我有以下功能:
int GetGroup(unsigned bitResult, int iStartPos, int iNumOfBites)
{
return (bitResult >> (iStartPos + 1- iNumOfBites)) & ~(~0 << iNumOfBites);
}
Run Code Online (Sandbox Code Playgroud)
该函数返回一个字节的位组.
也就是说,如果bitResult=102 (01100110)2, iStartPos=5, iNumOfBites=3
输出:2 (10)2
对于iStartPos=7, iNumOfBites=4
输出:3 (0110)2
我在寻找更好的方法/"友好"要做到这一点,即用bitset或类似的东西.
有什么建议吗?
(src >> start) & ((1UL << len)-1) // or 1ULL << if you need a 64-bit mask
Run Code Online (Sandbox Code Playgroud)
是表达位提取的一种方法len,从 开始start。(在本例中,start是您想要的范围的 LSB。您的函数需要 MSB 作为输入。)该表达式来自Wikipedia 关于 x86 BMI1 指令集扩展的文章。
len不过,如果是全宽的类型,这两种制作遮罩的方法看起来都有风险。(提取所有位的极端情况)。按类型的整个宽度移动可以产生零或不变。(它实际上调用了未定义的行为,但实际上,如果编译器在编译时看不到这一点,就会发生这种情况。例如,x86 将移位计数屏蔽到 0-31 范围(对于 32 位移位)。对于 32 位整数:
如果 1 << 32 产生 1,则 1-1 = 0,因此结果为零。
如果 ~0 << 32 产生 ~0,而不是 0,则掩码将为零。
请记住,太大1<<len是未定义的行为:与将其编写为或其他任何内容不同,不会发生自动升级,因此类型很重要。len0x3fffffffffflong long1
我认为从你的例子中你想要的位[iStartPos : iStartPos - iNumOfBites],其中位从零开始编号。
我在函数中更改的主要内容是函数和变量的命名,并添加注释。
bitResult是函数的输入;不要在名称中使用“结果”。iStartPos好的,但是有点冗长iNumOfBites计算机有位和字节。如果您正在处理咬伤,则需要医生(或牙医)。另外,返回类型可能应该是unsigned.
// extract bits [msb : msb-len] from input into the low bits of the result
unsigned BitExtract(unsigned input, int msb, int len)
{
return (input >> (msb-len + 1)) & ~(~0 << len);
}
Run Code Online (Sandbox Code Playgroud)
如果您的起始位置参数是 lsb,而不是 msb,则表达式会更简单,并且代码会更小、更快(除非这只会为调用者带来额外的工作)。使用 LSB 作为参数时,BitExtract 是 7 条指令,而如果是 MSB,则为 9 条指令(在 x86-64、gcc 5.2 上)。
还有一个机器指令(随 Intel Haswell 和 AMD Piledriver 引入)可以执行此操作。使用它你会得到更小、更快的代码。它还使用 LSB、len 位置约定,而不是 MSB,因此您可以使用 LSB 作为参数获得更短的代码。
Intel CPU 只知道需要首先将立即数加载到寄存器中的版本,因此当值是编译时常量时,与简单的移位和掩码相比,它不会节省太多。 例如,请参阅这篇关于使用它或 pextr for RGB32 -> RGB16 的文章。当然,如果 start 和 len 都是编译时常量,则参数是所需范围的 MSB 还是 LSB 并不重要。
只有 AMD 实现的版本bextr可以将控制掩码作为立即常量,但不幸的是,对于使用内部函数的代码,似乎 gcc 5.2 不使用立即版本(即使使用-march=bdver2(即推土机 v2 又名打桩机)。(它将在某些情况下,使用 生成带有直接参数的 beextr-march=bdver2。)
我在 godbolt 上对其进行了测试,看看使用或不使用 bextr 会得到什么样的代码。
#include <immintrin.h>
// Intel ICC uses different intrinsics for bextr
// extract bits [msb : msb-len] from input into the low bits of the result
unsigned BitExtract(unsigned input, int msb, int len)
{
#ifdef __BMI__ // probably also need to check for __GNUC__
return __builtin_ia32_bextr_u32(input, (len<<8) | (msb-len+1) );
#else
return (input >> (msb-len + 1)) & ~(~0 << len);
#endif
}
Run Code Online (Sandbox Code Playgroud)
需要额外的指令(a movzx)来实现(msb-len+1)&0xff安全检查,以避免起始字节溢出到长度字节中。我省略了它,因为要求 0-31 范围之外的起始位是无意义的,更不用说 0-255 范围了。既然它不会崩溃,只是返回一些其他无意义的结果,那就没有什么意义了。
不管怎样,bext节省了相当多的指令(如果 BMI2 shlx/shrx也不可用! -march=nativegodbolt 上是 Haswell,因此也包括 BMI2。)
但bextr在 Intel CPU 上解码为 2 uops ( http://agner.org/optimize/shrx ),因此与/相比and,除了节省一些代码大小之外, 它根本没有多大用处。pext实际上对于吞吐量(1 uop / 3c 延迟)更好,尽管它是一种更强大的指令。不过,延迟情况更糟。AMD CPU 运行速度pext非常慢,但bextr作为单个微指令。