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