为什么 Java 布尔值的大小必须至少为 1 个字节?

Why must Java booleans be at least 1 byte in size?

众所周知,C++ bools 的大小必须至少为 1 个字节,以便可以为每个 . But there are no pointers to primitive types in Java. Yet, they still take up at least 1 byte.

创建指针

为什么会这样 - 为什么 Java boolean 的大小不能为 1 位?除了计算时间,如果有一个大的 boolean 数组,肯定可以设想一个编译器进行适当的移位以检索对应于 boolean 值的各个位?

布尔值 没有理由必须 为一个字节。事实上,在某些情况下,布尔值的(有效)大小可能已经不是 1 字节:当在堆栈或对象中与大于 1 字节的其他元素一起打包时,它们可能是 更大(即,将它们添加到一个对象可能会导致大小增加超过一个字节)。

任何 JVM 都可以自由地将布尔值实现为 1 位,但据了解 none 选择这样做,可能主要是因为:

  • 访问一个位通常比访问一个字节更昂贵,尤其是在写入时。

    要读取位,使用 "classic RISC" 指令集的 CPU 通常需要额外的 and 指令来从打包字节(或更大的字节)中提取相关位字)的布尔位。有些甚至可能需要额外的指令来将常量加载到 and。在索引数组 boolean, where the bit-index isn't fixed at compile-time, you'd need a variable shift. Some CPUs such as x86 have an easier time since they have memory sourcetestinstructions, including specific bit-test instructions taking a variable position such asbt 的情况下。这样的 CPU 可能在两种表示中具有相似的读取性能。

    写入更糟糕:您现在需要读取值、修改适当的位并将其写回,而不是通过简单的字节写入来设置 boolean 值。一些平台(例如 x86)具有内存源和目标 RMW 指令,例如 andor 会有所帮助,但这些仍然比普通写入昂贵得多。在最坏的情况下,重复写入相同的元素将导致通过内存的依赖链,这可能会使您的代码速度降低一个数量级(一系列普通存储不能形成依赖链)。

    更糟糕的是,上面的写方法是完全线程不安全的。处理 "independent" 布尔值的两个线程可能会互相破坏,因此运行时必须使用原子更新操作来为任何无法证明对象在线程本地的字段写入位。

  • 数组外的 space 节省通常非常小,通常为零:对齐问题意味着单个位通常最终会采用与 space 相同的结果堆栈上或对象布局中的一个字节。只有当堆栈或对象上有许多原始 boolean 值时,您才会看到节省(例如,对象通常与 8 字节边界对齐,因此如果您有一个对象,其非布尔字段是 int 或更大,你至少需要 4 个 boolean 值来保存任何 space,通常你需要 8).

  • 这留下最后剩余的 "big win" 用于 boolean 数组中的位表示 boolean,您可以在其中获得渐近 8x space节省大型阵列。事实上,这种情况在 C++ 世界中足以激励 vector<bool> 有一个 "special" 实现,其中每个 bool 占用一位 - 由于所有必需的特殊要求,这是一个永无止境的头痛源案例和非直觉行为(并且经常用作现在无法删除的错误功能的示例)。

    如果没有内存模型,我可以想象一个 Java 的世界 以位方式实现 boolean 的数组。他们没有 与 vector<bool> 相同的问题(主要是因为 JIT 提供了额外的抽象层,还因为数组提供了比 vector 更简单的接口)并且我认为它可以高效地完成。虽然有那个讨厌的内存模型。如果由不同的线程完成,该模型允许写入不同的数组元素是安全的(即,出于内存模型的目的,它们充当独立变量)。如果将 boolean 实现为字节,所有常见的 CPU 都直接支持它,因为它们具有独立的字节访问。尽管没有 CPUs 提供独立的位访问:你被困在使用原子操作(x86 提供 lock bt* 操作,但这些操作很慢:其他平台有更糟糕的选择)。这会破坏任何作为位数组实现的布尔数组的性能。

最后,如上所述,将 boolean 实施为一个位具有显着的缺点 - 但优点呢?

事实证明,如果用户真的想要这种布尔值的位压缩表示,他们可以自己实现!他们可以将 8 个布尔值打包到对象中的 byte (或将 32 个值打包到 int 或其他任何东西)(这对于标志等很常见)并且生成的访问器代码应该是高效的高效,就好像 JVM 本机支持布尔值一样。事实上,当你 知道 你想要大量布尔值的位数组表示时,你可以简单地使用 BitSet - 这有你想要的表示和回避通过不提供任何线程安全保证来解决原子问题。因此,通过将 boolean 实现为一个字节,您可以回避上述所有问题,但仍然可以让用户 "opt-in" 在需要时使用位级表示,而不会造成太多运行时损失。