在设计java类时,有哪些建议可以实现CPU缓存友好性?
到目前为止我学到的是应该尽可能多地使用POD(即int而不是整数)。因此,在分配包含对象时将连续分配数据。 E.g。
class Local
{
private int data0;
private int data1;
// ...
};
比
更加缓存友好class NoSoLocal
{
private Integer data0;
private Integer data1;
//...
};
后者将需要对Integer对象进行两次单独的分配,这些对象可以位于内存中的任意位置,尤其是。 GC运行后。 OTOH第一种方法可能会在数据可以重复使用的情况下导致数据重复。
有没有办法让它们在内存中彼此靠近,以便父对象及其'包含元素一次在CPU缓存中,而不是在整个内存中任意分布加上GC将它们保持在一起?
答案 0 :(得分:5)
您无法强制JVM将相关对象彼此靠近放置(尽管JVM会尝试自动执行此操作)。但是有一些技巧可以使Java程序更易于缓存。
让我向您展示现实生活中的一些例子。
请注意!这不是推荐的Java编码方式! 除非你完全确定为什么要这样做,否则不要采用以下技术。
对作文的继承。你肯定听过相反的原则"Favor composition over inheritance"。但是通过合成,您可以额外参考。这不利于缓存局部性,也需要更多内存。 继承优于组合的经典示例是JDK 8 Adder和Accumulator类,它们扩展了实用程序Striped64
类。
将结构数组转换为数组结构。这再次有助于节省内存并加速单个字段上的批量操作,例如:密钥查找:
class Entry {
long key;
Object value;
}
Entry[] entries;
将替换为
long[] keys;
Object[] values;
通过内联展平数据结构。我最喜欢的示例是内联byte[]
表示的160位SHA1哈希。之前的代码:
class Blob {
long offset;
int length;
byte[] sha1_hash;
}
以下代码:
class Blob {
long offset;
int length;
int hash0, hash1, hash2, hash3, hash4;
}
将 String
替换为 char[]
。您知道,Java中的String
包含char[]
对象。为什么要为额外的参考支付性能损失?
避免链接列表。链接列表对缓存不友好。硬件最适合线性结构。 LinkedList
经常可以替换为ArrayList
。经典HashMap
可以替换为open address hash table。
使用原始集合。 Trove是一个高性能库,包含原始类型的专用列表,映射,集等。
在数组或ByteBuffers之上构建自己的数据布局。字节数组是一个完美的线性结构。要实现最佳缓存局部性,可以手动将对象数据打包到单个字节数组中。
答案 1 :(得分:1)
在可以重复使用数据的情况下,第一种方法可能会导致数据重复。
但不是你提到的情况。 int
是4个字节,引用通常是4个字节,因此使用Integer不会获得任何结果。对于更复杂的类型,它可以产生很大的不同。
有没有办法让它们在内存中彼此靠近,以便父对象及其'包含元素一次在CPU缓存中,而不是在整个内存中任意分配加上GC将它们保持在一起?
GC无论如何都会这样做,只要对象只在一个地方使用。如果对象在多个地方使用,它们将接近一个参考。
注意:不能保证这种情况,但是在分配对象时,它们通常在内存中是连续的,因为这是最简单的分配策略。复制保留的对象时,HotSpot GC将以与发现相反的顺序复制它们。即它们仍在一起,但顺序相反。
注2:对int
使用4个字节仍然比对整数使用28个字节更有效(4个字节用于引用,16个字节用于对象头,4个字节用于值,4个字节用于填充)
注3:最重要的是,除非你已经测量了你的需要并拥有更高效的解决方案,否则你应该倾向于清晰度而不是性能。在这种情况下,int
不能为空,但integer
可以为空。如果您想要一个不应null
使用int
的值,则不是为了表现而是为了清晰。