我有以下功能:
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
我正在寻找更好的方式/&#34;友好&#34;这样做,即用bitset
或类似的东西。
有任何建议吗?
答案 0 :(得分:4)
(src >> start) & ((1UL << len)-1) // or 1ULL << if you need a 64-bit mask
是从len
开始表达start
位提取的一种方法。 (在这种情况下,start
是您想要的范围的LSB。您的函数需要MSB作为输入。)此表达式来自Wikipedia's article on the x86 BMI1 instruction set extensions。
但是,如果len
是该类型的整个宽度,则生成掩码的两种方式都存在风险。 (提取所有位的角落情况)。按类型的整个宽度移位可以产生零或不变。 (它实际上调用了未定义的行为,但实际上如果编译器在编译时无法看到它会发生什么。例如,x86将移位计数屏蔽到0-31范围(对于32位移位)。使用32位注意:
如果1&lt;&lt; 32产生1,然后1-1 = 0,因此结果将为零。
如果~0&lt;&lt; 32产生〜0而不是0,掩码将为零。
请记住1<<len
len
的未定义行为太大了:不像将其写为0x3ffffffffff
或其他任何内容,都不会自动升级到long long
,所以类型1
很重要。
我认为从您的示例中您需要位[iStartPos : iStartPos - iNumOfBites]
,其中位从零开始编号。
我在函数中更改的主要内容是函数和变量的命名,并添加注释。
bitResult
是函数的输入;不要使用&#34;结果&#34;以它的名字。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);
}
如果你的起始位置参数是lsb而不是msb,表达式会更简单,代码会更小更快(除非这只会给调用者带来额外的工作)。使用LSB作为参数,BitExtract是7条指令,如果它是MSB(在x86-64,gcc 5.2上),则为9条。
还有一个机器指令(由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并没有将立即版本用于使用内在函数的代码(即使使用{ {1}}(即推土机v2又名打桩机)。(generate bextr with an immediate argument on its own in some cases与-march=bdver2
合作。)
我tested it out on godbolt了解您使用或不使用bextr可获得的代码类型。
-march=bdver2
需要额外的指令(#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
}
)来实现movzx
安全检查,以避免起始字节溢出到长度字节中。我把它排除在外是因为要求0-31范围之外的起始位是无稽之谈,更不用说0-255范围了。由于它不会崩溃,只是返回一些其他废话结果,没有多大意义。
无论如何,(msb-len+1)&0xff
保存了不少指令(如果BMI2 bext
/ shlx
也不可用!在Godbolt上的shrx
是Haswell,因此包括BMI2也是如此。)
但是英特尔CPU上的 -march=native
解码为2 uops (http://agner.org/optimize/),所以与bextr
/相比它根本不是很有用shrx
,但保存一些代码大小除外。 and
实际上对吞吐量(1 uop / 3c延迟)更好,即使它是一种更强大的指令。但是,延迟更糟糕。并且AMD CPU的运行速度非常慢pext
,但pext
为单个uop。
答案 1 :(得分:1)
pragma pack(push, 1)
struct Bit
{
union
{
uint8_t _value;
struct {
uint8_t _bit0:0;
uint8_t _bit1:0;
uint8_t _bit2:0;
uint8_t _bit3:0;
uint8_t _bit4:0;
uint8_t _bit5:0;
uint8_t _bit6:0;
uint8_t _bit7:0;
};
};
};
#pragma pack(pop, 1)
typedef Bit bit;
struct B
{
union
{
uint32_t _value;
bit bytes[1]; // 1 for Single Byte
};
};
使用struct和union,您可以将Struct B _value设置为结果,然后为每个0或1访问byte [0] ._ bit0到byte [0] ._ bit7,反之亦然。设置每个位,结果将在_value中。
答案 2 :(得分:1)
我可能会做以下的事情,以便为参数中的错误提供额外的保护,并减少转移的数量。
我不确定我是否理解您使用的参数的含义,因此可能需要稍微调整一下。
我不确定这是否必须更有效率,因为为安全起见,有许多决定和范围检查。
/*
* Arguments: const unsigned bitResult byte containing the bit field to extract
* const int iStartPos zero based offset from the least significant bit
* const int iNumOfBites number of bits to the right of the starting position
*
* Description: Extract a bitfield beginning at the specified position for the specified
* number of bits returning the extracted bit field right justified.
*/
int GetGroup(const unsigned bitResult, const int iStartPos, const int iNumOfBites)
{
// masks to remove any leading bits that need to disappear.
// we change starting position to be one based so the first element is unused.
const static unsigned bitMasks[] = {0x01, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff};
int iStart = (iStartPos > 7) ? 8 : iStartPos + 1;
int iNum = (iNumOfBites > 8) ? 8 : iNumOfBites;
unsigned retVal = (bitResult & bitMasks[iStart]);
if (iStart > iNum) {
retVal >>= (iStart - iNum);
}
return retVal;
}