是的,但是……
这当然可能,但通常是荒谬的(对于任何不使用 十亿 个这些数字的程序):
#include <stdint.h> // don't want to rely on something like long long
struct bad_idea
{
uint64_t var : 40;
};
这里,var 确实有 40 位的宽度,但会产生 much 效率较低的代码(事实证明,“much”是非常错误的——测量的开销是仅 1-2%,请参见下面的时间安排),通常无济于事。除非您需要将另一个 24 位值(或 8 位和 16 位值)打包到同一结构中,否则对齐将失去您可能获得的任何东西。
无论如何,除非您有数十亿个这样的内存,否则内存消耗的有效差异不会很明显(但管理位字段所需的额外代码会很明显!)。
注意:
同时,该问题已更新,以反映确实需要 十亿 个数字,因此这可能是可行的做法,假设您采取措施不因结构对齐而失去收益和填充,即通过在剩余的 24 位中存储其他内容或将 40 位值存储在每个 8 位或其倍数的结构中)。
节省三个字节十亿次是值得的,因为它将需要明显更少的内存页面,从而导致更少的缓存和 TLB 未命中,尤其是页面错误(单个页面错误加权数千万条指令)。
虽然上面的 sn-p 没有使用剩余的 24 位(它仅演示了“使用 40 位”部分),但需要类似于以下内容才能真正使该方法在保留的意义上有用记忆——假设你确实有其他“有用”的数据可以放入洞中:
struct using_gaps
{
uint64_t var : 40;
uint64_t useful_uint16 : 16;
uint64_t char_or_bool : 8;
};
结构大小和对齐方式将等于 64 位整数,因此如果您制作例如十亿个此类结构的数组(即使不使用特定于编译器的扩展)。如果您不使用 8 位值,也可以使用 48 位和 16 位值(提供更大的溢出余量)。
或者,您可以以牺牲可用性为代价,将 8 个 40 位值放入一个结构中(40 和 64 的最小公倍数为 320 = 8*40)。当然,访问结构数组中的元素的代码将变得非常更加复杂(尽管可以实现一个operator[] 来恢复线性数组功能并隐藏结构复杂性)。 p>
更新:
编写了一个快速测试套件,只是为了看看位域(以及使用位域引用重载的运算符)会有什么开销。在gcc.godbolt.org 发布代码(由于长度),我的 Win7-64 机器的测试输出是:
Running test for array size = 1048576
what alloc seq(w) seq(r) rand(w) rand(r) free
-----------------------------------------------------------
uint32_t 0 2 1 35 35 1
uint64_t 0 3 3 35 35 1
bad40_t 0 5 3 35 35 1
packed40_t 0 7 4 48 49 1
Running test for array size = 16777216
what alloc seq(w) seq(r) rand(w) rand(r) free
-----------------------------------------------------------
uint32_t 0 38 14 560 555 8
uint64_t 0 81 22 565 554 17
bad40_t 0 85 25 565 561 16
packed40_t 0 151 75 765 774 16
Running test for array size = 134217728
what alloc seq(w) seq(r) rand(w) rand(r) free
-----------------------------------------------------------
uint32_t 0 312 100 4480 4441 65
uint64_t 0 648 172 4482 4490 130
bad40_t 0 682 193 4573 4492 130
packed40_t 0 1164 552 6181 6176 130
我们可以看到,位域的额外开销可以忽略不计,但是当以缓存友好的方式线性访问数据时,作为一种方便的方式使用位域引用重载的运算符是相当剧烈的(大约增加了 3 倍)。另一方面,在随机访问中,它几乎不重要。
这些时间表明,简单地使用 64 位整数会更好,因为它们总体上仍然比位域更快(尽管涉及更多内存),但当然它们没有考虑更大数据集的页面错误成本。一旦你的物理 RAM 用完(我没有测试过),它看起来可能会有很大不同。