为什么 std::bitset<8> 4 个字节大?

Why is std::bitset<8> 4 bytes big?

本文关键字:字节 gt std lt 为什么 bitset      更新时间:2023-10-16

似乎对于std::bitset<1到32>,大小设置为4字节。对于33到64的大小,它直接跳到8字节。不可能有任何开销,因为std::bitset<32>是一个偶数4字节。

当处理位时,我可以看到对齐到字节长度,但是为什么bitset需要对齐到字长度,特别是对于最有可能在内存预算紧张的情况下使用的容器?

最可能的解释是bitset使用了整组机器字来存储数组。

这可能是出于内存带宽的原因:读/写在单词边界对齐的单词通常相对便宜。另一方面,在某些体系结构中,读取(尤其是写入!)任意对齐的字节可能会非常昂贵。

由于我们讨论的是每个bitset固定大小的几个字节的损失,这听起来像是通用库的合理权衡。

我假设对bitset的索引是通过抓取一个32位值,然后隔离相关的位来完成的,因为就处理器指令而言,这是最快的(在x86上处理较小的值会更慢)。这需要的两个索引也可以很快地计算出来:

int wordIndex = (index & 0xfffffff8) >> 3;
int bitIndex = index & 0x7;

然后你可以这样做,这也是非常快的:

int word = m_pStorage[wordIndex];
bool bit = ((word & (1 << bitIndex)) >> bitIndex) == 1;

另外,每个bitset最大浪费3字节并不完全是内存问题。考虑到bitset已经是存储这类信息的最有效的数据结构,因此您必须以总结构大小的百分比来评估浪费。

对于1025位,这种方法使用132字节而不是129字节,为2.3%的开销(并且随着bitset站点的增加而降低)。考虑到可能带来的性能好处,这听起来很合理。

现代机器上的内存系统除了从内存中提取所需位的一些遗留函数外,不能从内存中提取任何其他内容。因此,将bitset与单词对齐可以使它们的处理速度快得多,因为在访问它时不需要屏蔽不需要的位。如果不使用掩码,请输入

bitset<4> foo = 0;
if (foo) {
    // ...
}

很可能会失败。除此之外,我记得前一段时间读到有一种方法可以将几个bitset挤在一起,但我记不清了。我认为当你在一个结构中有几个位集在一起时,它们可以占用"共享"内存,这不适用于比特域的大多数用例。

我在Aix和Linux实现中有相同的特性。在Aix中,内部bitset存储是基于char的:

typedef unsigned char _Ty;
....
_Ty _A[_Nw + 1];

在Linux中,内部存储是基于long的:

typedef unsigned long _WordT;
....
_WordT            _M_w[_Nw];

出于兼容性的考虑,我们修改了Linux版本,使用基于字符的存储

检查在bitset.h

中使用的是哪个实现

因为32位的intel兼容处理器不能单独访问字节(或者更好的是,它可以通过隐式地应用一些位掩码和移位),而每次只能访问32位的字。

如果你声明

bitset<4> a,b,c;
即使库将

实现为char, a, bc也将以32位对齐,因此存在相同的浪费空间。但是处理器将被迫在让bitset代码执行自己的掩码之前对字节进行预掩码。

因此MS使用int[1+(N-1)/32]作为位的容器

也许是因为它默认使用int,并切换到long long,如果它溢出?(只是猜测…)

如果你的std::bitset<如果8>是结构体的成员,你可以这样写:

struct A
{
  std::bitset< 8 > mask;
  void * pointerToSomething;
}

如果bitset<8>存储在一个字节中(并且结构体封装在1字节边界上),那么结构体中跟随它的指针将是未对齐的,这将是一件坏事。将bitset<8>存储在一个字节中是安全且有用的唯一情况是,它在一个打包结构中,并且后面跟着一些可以打包在一起的其他单字节字段。我想这个用例太窄了,不值得提供一个库实现。

基本上,在你的八叉树中,一个单字节的bitset只有在一个打包结构中后跟另外一个到三个单字节的成员时才有用。否则,无论如何都必须将其填充为四个字节(在32位机器上),以确保下面的变量与单词对齐。