从比特数组中提取的更聪明的方法是什么?

时间:2010-01-29 08:29:17

标签: c++ c optimization bit-manipulation

我的内存区域可以被认为是“位数组”。它们相当于

unsigned char arr[256];

但最好将其视为

bit arr[2048];

我用

访问它的单独位
#define GETBIT(x,in)   ((in)[ ((x)/8) ] & 1<<(7-((x)%8)))

但是我在代码的许多地方做了很多,通常是在性能关键的部分,我想知道是否有更聪明,更优化的方法。

额外信息:架构:ARM9(32位); GCC / Linux操作系统。无法更改物理数据表示 - 从外部提供或映射以供外部使用。

8 个答案:

答案 0 :(得分:7)

我不这么认为。实际上,许多CPU架构都不会单独访问位。

在C ++上你有std::bitset<N>但可能没有最高性能,具体取决于编译器的实现和优化。

顺便说一下,最好将您的位数组分组为uint32_t[32](或uint64_t[16])以进行对齐解除引用(bitset已为您执行此操作)。

答案 1 :(得分:6)

对于随机访问单个位,您建议的宏与您将获得的一样好(只要您在编译器中启用优化)。

如果您正在访问的位有任何模式,那么您可以做得更好。例如,如果您经常访问位,那么通过提供一个获取两位而不是一位的方法,您可能会看到一些改进,即使您并不总是最终使用这两位。 / p>

与任何优化问题一样,您需要非常熟悉代码的行为,特别是其位数组中的访问模式,以便在性能方面做出有意义的改进。

更新:由于您可以访问位范围,因此您可以从宏中获得更多性能。例如,如果您需要访问四位,则可能具有以下宏:

#define GETBITS_0_4(x,in) (((in)[(x)/8] & 0x0f))
#define GETBITS_1_4(x,in) (((in)[(x)/8] & 0x1e) >> 1)
#define GETBITS_2_4(x,in) (((in)[(x)/8] & 0x3c) >> 2)
#define GETBITS_3_4(x,in) (((in)[(x)/8] & 0x78) >> 3)
#define GETBITS_4_4(x,in) (((in)[(x)/8] & 0xf0) >> 4)
#define GETBITS_5_4(x,in) ((((in)[(x)/8] & 0xe0) >> 5) | (((in)[(x)/8+1] & 0x01)) << 3)
#define GETBITS_6_4(x,in) ((((in)[(x)/8] & 0xc0) >> 6) | (((in)[(x)/8+1] & 0x03)) << 2)
#define GETBITS_7_4(x,in) ((((in)[(x)/8] & 0x80) >> 7) | (((in)[(x)/8+1] & 0x07)) << 1)
// ...etc

这些宏会从每个位位置0,1,2等中删除4位。(为了减少无意义括号的扩散,您可能希望使用内联函数来实现上述。)然后可能定义内联函数功能如:

inline int GETBITS_4(int x, unsigned char *in) {
    switch (x % 8) {
        case 0: return GETBITS_0_4(x,in);
        case 1: return GETBITS_1_4(x,in);
        case 2: return GETBITS_2_4(x,in);
        // ...etc
    }
}

由于这是一个繁琐的样板代码,特别是如果你有多个不同的宽度,你可能想编写一个程序来生成所有GETBIT_*访问器函数。

(我注意到你的字节中的位以与我上面写的相反的顺序存储。如果需要,应用适当的转换来匹配你的结构。)

答案 2 :(得分:3)

以格雷格的解决方案为基础:

template<unsigned int n, unsigned int m> 
inline unsigned long getbits(unsigned long[] bits) {
  const unsigned bitsPerLong = sizeof(unsigned long) * CHAR_BIT
  const unsigned int bitsToGet = m - n;
  BOOST_STATIC_ASSERT(bitsToGet < bitsPerLong);
  const unsigned mask = (1UL << bitsToGet) - 1;
  const size_t index0 = n / bitsPerLong;
  const size_t index1 = m / bitsPerLong;
  // Do the bits to extract straddle a boundary?
  if (index0 == index1) {
    return (bits[index0] >> (n % bitsPerLong)) & mask;
  } else {
    return ((bits[index0] >> (n % bitsPerLong)) + (bits[index1] << (bitsPerLong - (m % bitsPerLong)))) & mask;
  }
}

即使它们没有对齐,也可以获得至少32位。请注意,故意inline,因为您不希望拥有大量这些功能。

答案 3 :(得分:1)

如果您反转'array'中的位顺序,则可以消除宏中的减法。这是我能说的最好的,不知道问题的上下文(如何使用这些位)。

答案 4 :(得分:1)

#define GETBIT(x,in)   ((in)[ ((x)/8) ] & 1<<(7-((x)%8)))

可以进行优化。

1)使用标准int,它通常是最快的可访问整数数据类型。    如果您不需要可移植,可以找出int的大小    sizeof并调整以下代码。

2)

#define GETBIT(x,in)   ((in)[ ((x) >>> 3) ] & 1<<((x) & 7))

mod运算符%比ANDing慢。而且你不需要减去, 只需调整SETBIT例程即可。

答案 5 :(得分:0)

为什么不创建自己的包装类?

然后,您可以使用+等运算符向“数组”添加位,并使用[]运算符返回各个位。

使用&amp;可以改善您的宏7而不是%8,但编译器可能会为您进行优化。

我最近做了你正在做的事情,我的流可以包含任意数量的比特。

所以我有以下内容:

BitStream< 1 > oneBitBitStream;
BitStream< 2 > twoBitBitStream;

oneBitBitStream += Bit_One;
oneBitBitStream += Bit_Zero;

twoBitBitStream += Bit_Three;
twoBitBitStream += Bit_One;

等等。它提供了良好的可读代码,您可以为它提供类似STL的接口以帮助相似性:)

答案 6 :(得分:0)

由于问题是用C ++标记的,您有什么理由不能简单地使用标准bitset吗?

答案 7 :(得分:0)

您可以使用std::vector<bool>代替unsigned char数组和自定义宏。向量类模板具有bool类型的特殊模板特化。这个专门化是为了优化空间分配:在这个模板专门化中,每个元素只占一位(比C ++中最小的类型小八倍:char)。