从字节中获取位
Get bits from byte
我有以下功能:
int GetGroup(unsigned bitResult, int iStartPos, int iNumOfBites)
{
return (bitResult >> (iStartPos + 1- iNumOfBites)) & ~(~0 << iNumOfBites);
}
函数 returns 来自一个字节的一组位。
即如果 bitResult=102 (01100110)2, iStartPos=5, iNumOfBites=3
输出:2 (10)2
对于iStartPos=7, iNumOfBites=4
输出:3 (0110)2
我正在寻找更好的方法/"friendly" 来做到这一点,即使用 bitset
或类似的方法。
有什么建议吗?
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 B _value 设置为您的结果,然后为每个 0 或 1 访问 byte[0]._bit0 到 byte[0]._bit7,反之亦然。设置每一位,结果将在 _value.
我可能会做类似下面的事情,以便为参数中的错误提供额外的保护并减少移位量。
我不确定我是否理解您使用的参数的含义,所以这可能需要一些调整。
而且我不确定这是否一定更有效,因为为了安全起见,我们做出了许多决定和范围检查。
/*
* 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;
}
(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 << 32 产生 1,则 1-1 = 0,所以结果将为零。
如果 ~0 << 32 产生 ~0,而不是 0,掩码将为零。
请记住,1<<len
是 len
太大的未定义行为:与将其写为 0x3ffffffffff
或其他任何内容不同,不会自动升级到 long long
,因此类型的 1
事项。
我认为从您的示例中您需要位 [iStartPos : iStartPos - iNumOfBites]
,其中位从零开始编号。
我要在您的函数中更改的主要内容是函数和变量的命名,并添加注释。
bitResult
是函数的输入;不要在其名称中使用 "result"。
iStartPos
好的,但有点冗长
iNumOfBites
计算机有比特和字节。如果您正在处理咬伤,则需要医生(或牙医)。
此外,return类型应该是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 则为 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。当然,参数是所需范围的 MSB 还是 LSB 并不重要,如果 start 和 len 都是编译时常量。
只有 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 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
)来实施(msb-len+1)&0xff
安全检查以避免起始字节溢出到长度字节。我把它排除在外是因为要求 0-31 范围之外的起始位是无稽之谈,更不用说 0-255 范围了。既然不会崩溃,就return其他一些乱七八糟的结果,意义不大。
无论如何,bext
节省了很多指令(如果 BMI2 shlx
/ shrx
也不可用!-march=native
在 godbolt 上是 Haswell,因此包括BMI2 也是如此。)
但 Intel CPU 上的 bextr
解码为 2 uops (http://agner.org/optimize/),因此与 shrx
相比它根本不是很有用/ and
,除了节省一些代码大小。 pext
对于吞吐量(1 uop / 3c 延迟)实际上更好,尽管它是一种更强大的指令。但是,延迟更糟。和 AMD CPU 运行 pext
非常慢,但是 bextr
作为一个 uop。
我有以下功能:
int GetGroup(unsigned bitResult, int iStartPos, int iNumOfBites)
{
return (bitResult >> (iStartPos + 1- iNumOfBites)) & ~(~0 << iNumOfBites);
}
函数 returns 来自一个字节的一组位。
即如果 bitResult=102 (01100110)2, iStartPos=5, iNumOfBites=3
输出:2 (10)2
对于iStartPos=7, iNumOfBites=4
输出:3 (0110)2
我正在寻找更好的方法/"friendly" 来做到这一点,即使用 bitset
或类似的方法。
有什么建议吗?
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 B _value 设置为您的结果,然后为每个 0 或 1 访问 byte[0]._bit0 到 byte[0]._bit7,反之亦然。设置每一位,结果将在 _value.
我可能会做类似下面的事情,以便为参数中的错误提供额外的保护并减少移位量。
我不确定我是否理解您使用的参数的含义,所以这可能需要一些调整。
而且我不确定这是否一定更有效,因为为了安全起见,我们做出了许多决定和范围检查。
/*
* 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;
}
(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 << 32 产生 1,则 1-1 = 0,所以结果将为零。
如果 ~0 << 32 产生 ~0,而不是 0,掩码将为零。
请记住,1<<len
是 len
太大的未定义行为:与将其写为 0x3ffffffffff
或其他任何内容不同,不会自动升级到 long long
,因此类型的 1
事项。
我认为从您的示例中您需要位 [iStartPos : iStartPos - iNumOfBites]
,其中位从零开始编号。
我要在您的函数中更改的主要内容是函数和变量的命名,并添加注释。
bitResult
是函数的输入;不要在其名称中使用 "result"。iStartPos
好的,但有点冗长iNumOfBites
计算机有比特和字节。如果您正在处理咬伤,则需要医生(或牙医)。
此外,return类型应该是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 则为 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。当然,参数是所需范围的 MSB 还是 LSB 并不重要,如果 start 和 len 都是编译时常量。
只有 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 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
)来实施(msb-len+1)&0xff
安全检查以避免起始字节溢出到长度字节。我把它排除在外是因为要求 0-31 范围之外的起始位是无稽之谈,更不用说 0-255 范围了。既然不会崩溃,就return其他一些乱七八糟的结果,意义不大。
无论如何,bext
节省了很多指令(如果 BMI2 shlx
/ shrx
也不可用!-march=native
在 godbolt 上是 Haswell,因此包括BMI2 也是如此。)
但 Intel CPU 上的 bextr
解码为 2 uops (http://agner.org/optimize/),因此与 shrx
相比它根本不是很有用/ and
,除了节省一些代码大小。 pext
对于吞吐量(1 uop / 3c 延迟)实际上更好,尽管它是一种更强大的指令。但是,延迟更糟。和 AMD CPU 运行 pext
非常慢,但是 bextr
作为一个 uop。