int GetGroup(unsigned bitResult,int iStartPos,int iNumOfBites) { return (bitResult >> (iStartPos + 1- iNumOfBites)) & ~(~0 << iNumOfBites); }
该函数返回一个字节的位组.
即如果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
是从开始开始表达len位提取的一种方法. (在这种情况下,start是您想要的范围的LSB.您的函数需要MSB作为输入.)此表达式来自Wikipedia’s article on the x86 BMI1 instruction set extensions.
但是,如果len是该类型的整个宽度,则两种生产掩模的方式看起来都很冒险. (提取所有位的角落情况).按类型的整个宽度移位可以产生零或不变. (它实际上调用了未定义的行为,但实际上如果编译器在编译时看不到它会发生什么.例如,x86将移位计数屏蔽到0-31范围(对于32位移位).使用32位整数:
>如果1 <<< 32产生1,然后1-1 = 0,因此结果将为零.
>如果~0<< 32产生〜0,而不是0,掩码将为零.
请记住1< len是未定义的行为,因为它太大了:不像把它写成0x3ffffffffff或其他什么,没有自动升级到long long,所以1的类型很重要. 我想从你的例子中你想要的是位[iStartPos:iStartPos – iNumOfBites],其中位从零开始编号. 我在函数中改变的主要是函数和变量的命名,并添加注释.
> bitResult是函数的输入;不要在名称中使用“结果”.
> iStartPos好,但有点冗长
> iNumOfBites计算机具有位和字节.如果您正在处理叮咬,您需要医生(或牙医).
此外,返回类型可能应该是无符号的.
// 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); }
如果你的起始位置参数是lsb而不是msb,表达式会更简单,代码会更小更快(除非这只会给调用者带来额外的工作).使用LSB作为参数,BitExtract是7条指令,如果它是MSB,则为9条(在x86-64,gcc 5.2上).
还有一个机器指令(与Intel Haswell和AMD Piledriver一起引入)执行此操作.使用它可以获得更小,更快的代码.它还使用LSB,len位置约定,而不是MSB,因此您可以使用LSB作为参数获得更短的代码.
Intel cpu只知道需要先将一个立即加载到寄存器中的版本,所以当这些值是编译时常量时,与简单的移位和屏蔽相比,它不会节省太多. e.g. see this post about using it or pextr for RGB32 -> RGB16.当然,如果start和len都是编译时常数,那么参数是否是所需范围的MSB或LSB无关紧要.
只有AMD实现了一个版本的bextr,它可以将控制掩码作为一个直接常量,但不幸的是,似乎gcc 5.2不使用立即版本来使用内在的代码(即使用-march = bdver2(即推土机v2也称为打桩机) ).(generate bextr with an immediate argument on its own in some cases与-march = bdver2.)
我tested it out on 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 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 }
需要额外的指令(movzx)来实现(msb-len 1)& 0xff安全检查,以避免起始字节溢出到长度字节中.我把它排除在外因为要求0-31范围之外的起始位是毫无意义的,更不用说0-255范围了.既然它不会崩溃,只返回一些其他废话结果,没有多大意义.
无论如何,bext保存了不少指令(如果BMI2 shlx / shrx也不可用!-march = godbolt上的native是Haswell,因此也包括BMI2.)
但是英特尔cpu上的bextr解码为2 uop(http://agner.org/optimize/),因此除了保存一些代码大小之外,它与shrx /并不是很有用. pext实际上更好的吞吐量(1 uop / 3c延迟),即使它是一种更强大的指令.但是,延迟更糟糕. AMD cpu运行速度非常慢,但bextr作为单个uop.