在位数组中使用什么数据类型

What data type to use in a bit array

当在 C 中制作位数组或也称为位集时,我应该使用什么数据类型?我应该使用 int 的数组吗? unsigned intsize_tuint8_tuintmax_t?

对我来说,使用有符号整数类型是禁忌,正如 SO 中关于有符号整数左移和右移的其他答案所指出的那样(我在答案中丢失了 link)。

现在,我应该使用可用的最小无符号整数还是最大的?哪一个表现最好?

我的一些想法:SO 中的大多数位数组使用 charuint8_t 的数组,但我看不出这比使用 [= 更好14=] (也是因为我还没有看到任何关于为什么要走这条路的论据,因此这个问题)。当在两个位数组之间进行某些操作(如并集和交集)时,如果使用更大的无符号整数,循环迭代的次数会更少。

编辑:看到一些答案后,我认为有些人对我的问题感到困惑。对于那个很抱歉。我想要做的是制作一个位数组,其中每个位都可以单独访问或设置为 1 或 0。我知道我可以只使用 bool 数组,但这不是 space 高效的。你可以说现在我们有足够大的 RAM,并且位数组相对于布尔数组的增益是最小的,但这不是这里的重点。 我想知道的是,给定一个位数组,其中每个位都可以被 bit_index (不同于数组的索引)修改或访问,我的数组应该是什么数据类型?

你是对的。通常对位数组使用 charunsigned char。这背后的原因纯粹与效率有关。 char 仅保留 1 字节(8 位)的内存,而 int 通常需要 4 字节(32 位,这取决于您的系统和编译器) 你算一下。只需要存储一位,那么使用哪个更高效?

这取决于您需要跟踪多少位、访问单个位的效率以及跟踪所有这些位要消耗多少内存。

有很多方法可以做到这一点,没有进一步的细节很难回答。

我看到的是 uint32_t 的简单数组,以保持其紧凑和良好的访问速度。然后在访问位时,比如说第 128 位,这将是数组第 4 uint32_t 位的第 0 位。

What data type to use in a bit array (?)
... where each bit can be individually accessed or set to either 1 or 0.
... could just use a bool array but that is not space efficient.

你不能得到你想要的一切:需要做出权衡。


对于N位"array",各种方法

_Bool的数组:_Bool ar1[N];

  • Pro:易于索引:ar1[i]
  • 专业版:只有 2 个值。
  • 缺点:Space 低效 - 甚至可能比 unsigned char ar2[N];
  • 更糟糕

最小类型数组:unsigned char ar2[N];

  • Pro:易于索引:ar2[i]
  • 专业版:没有陷阱值,也没有填充。
  • 缺点:可以对值 0,1 和其他值进行编码。
  • 缺点:Space 低效

压缩数组unsigned charunsigned char ar3[(N+CHAR_BIT-1)/CHAR_BIT];

  • 专业人士:Space高效。
  • 专业版:没有陷阱值,也没有填充。
  • 缺点:需要辅助代码来索引:(ar3[i/CHAR_BIT] >> (i%CHAR_BIT))%2
  • 缺点:可能有一些额外的 "array" 元素。

压缩数组unsignedunsigned ar4[(N+UNSIGNED_BIT-1)/UNSIGNED_BIT];

  • 专业人士:Space高效。
  • 专业版:可能 fast/faster 比 ar3 使用原生 unsigned 类型。
  • 缺点:需要辅助代码来索引:(ar4[i/UNSIGNED_BIT] >> (i%UNSIGNED_BIT))%2
  • 缺点:可能有一些额外的 "array" 元素。
  • ***:迂腐地担心 unsigned 可能会被填充导致更复杂的位宽确定,因为 UNSIGNED_BIT 需要基于 UNSIGNED_MAX 而不是 CHAR_BIT.

结论

IMO,使用 _Bool ar1[N]; 直到 space/speed 被证明是一个问题。在这种情况下,我会转到 unsigned ar4[(N+UNSIGNED_BIT-1)/UNSIGNED_BIT];


For me, using integer types is a no-no as noted by other answers here in SO about signed integer left and right shifts

OP 的担忧在这里被夸大了。 signed 类型会出现主要的转换问题。请改用 unsigned 类型。


use an array of char or uint8_t, but I can't see how that would be better than using uintmax_t.

推测的 OP 在这里占用了一个 "packed" 位数组。

  • Con uintmax_t。它要求数组大小是 uintmax_t 位大小的倍数,而不是更容易匹配的 uint8_t。否则无论哪种方式都会浪费内存,uint8_t.

  • Con uint8_t。它并不总是可用(这是例外情况)。

  • Con char。可以签名

  • Con uint8_t。大概比 unsigned.

  • 慢或慢
  • Con uintmax_t。除非代码本身支持该宽类型,否则发出的代码可能比其他替代方案慢。

  • Con uintmax_t。宽类型更有可能需要多条缩小类型的指令。当然这在平台之间是不同的。

理想情况下,最好使用最广泛的本机类型 - 这通常是 unsigned

IMO,unsigned 是打包的最佳选择。

最好的选择是对尽可能多的方案进行基准测试。根据您打算存储的位数以及读写它们的频率,将每个位存储为 unsigned char(甚至 unsigned int)可能是有意义的,但是在 16+ 位 unsigned int 中将其中的 16 个打包得更紧,对于在效率和访问便利性之间取得良好的权衡是有意义的。 unsigned int 是一个不错的选择,但我不会推荐 unsigned int 除非您可以保证您的预期架构不使用填充位或任何意外陷阱值。任何现代架构都可能有 uint32_t(在 stdint.h 中定义),如果你不相信 unsigned int,这是我的建议,因为你知道它的确切大小并且保证没有填充位按标准。如果您知道您将 运行 您的代码放在 64 位架构上,uint64_t 将是更好的选择。如果可能,记得进行基准测试。

请注意,该标准要求对小于 int 的类型的所有操作隐式转换(在 C 抽象机中)为 int(或者 unsigned int,如果它不会' t 适合 int),然后再次转换回原始 _Boolcharshort。这有时会导致意外。

我个人会使用 size_t。对于大多数(不是全部,但可能是您关心的所有平台)平台,它与 CPU 寄存器的大小相同,这意味着对于需要扫描整个位向量的许多操作,它实现了每次循环迭代处理的最大位数(例如,查找设置位、计数位等)。对于此类算法,CPU 内置函数,如 bsf(向前扫描位)和 clz(计算前导零)将显着加快您的算法。

只是为了上下文,Linux 内核使用 unsigned long 作为位向量,AFAIK 与所有 Linux ABI 上的 size_t 相同,但不是Windows(至少在 MSVC 中没有)其中 long 是 32 位,即使在 x64 上也是如此。

一般来说,处理单个位时最有效的大小可能是 unsigned int。最大大小和寄存器大小可能效率低下(例如,在 64 位 80x86 上,64 位指令需要 "REX prefixes",这将导致毫无意义的膨胀,没有任何好处)。

对于处理整个位集(例如搜索、计数),如果性能首先很重要,那么大小基本上无关紧要。例如(对于 SSE2),您可以将 16 个 8 位整数装入一个 128 位寄存器,或者将八个 16 位整数装入一个 128 位寄存器,或者将四个 32 位整数装入一个 128 位寄存器,或者两个将 64 位整数转换为 128 位寄存器;对于所有这些情况,无论单个整数的大小如何,您都将进行 128 位运算。

然而,效率并不是唯一重要的事情,使用 "un-fixed sized integers"(如 unsigned int)意味着您需要使用宏/#define 来污染您的代码难以阅读(以 "Oh darn, I need to break my concentration and go track down some random piece of noise buried in a header file somewhere just to see what FOO actually is" 方式),而固定大小的整数类型(例如 uint32_t)将避免这种情况。具体来说,我会使用(并且已经使用)uint32_t,而不用太在意性能。

You could say that nowadays we have RAMs that are big enough and the gain of bit arrays over boolean arrays is minimal but that's not the point here.

你可以说 RAM 很大但相对较慢,缓存很小但相对较快,并且性能要求通过打包来最大限度地减少缓存未命中(以提高缓存的效率并减少相对较慢的 RAM 的使用)最多的数据变成最少的space。 ;)