调整java类以获得CPU缓存友好性

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将它们保持在一起?

apa*_*gin 5

您无法强制JVM将相关对象彼此靠近放置(尽管JVM会尝试自动执行此操作).但是有一些技巧可以使Java程序更易于缓存.

让我向您展示一些来自现实生活项目的例子.

谨防!这不是推荐的Java编码方式!
除非您完全确定为什么要这样做,否则不要采用以下技术.

  1. 对构图的继承.你肯定听过了相反的原则"赞成合成而非继承".但是通过合成,您可以额外参考.这不利于缓存局部性,也需要更多内存.继承于组合的经典示例是JDK 8 Adder和Accumulator类,它们扩展了实用程序Striped64类.

  2. 将结构数组转换为数组结构.这再次有助于节省内存并加速单个字段上的批量操作,例如密钥查找:

    class Entry {
        long key;
        Object value;
    }
    
    Entry[] entries;
    
    Run Code Online (Sandbox Code Playgroud)

    将被取代

    long[] keys;
    Object[] values;
    
    Run Code Online (Sandbox Code Playgroud)
  3. 通过内联展平数据结构.我最喜欢的例子是内联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)
  4. 替换 String char[].你知道,String在Java中包含char[]了引擎盖下的对象.为什么要为额外的参考支付性能损失?

  5. 避免链接列表.链接列表对缓存不友好.硬件最适合线性结构.LinkedList经常可以替换ArrayList.经典HashMap可以用开放地址哈希表替换.

  6. 使用原始集合. Trove是一个高性能的库,包含原始类型的专用列表,映射,集合等.

  7. 在数组或ByteBuffers之上构建自己的数据布局.字节数组是完美的线性结构.要获得最佳缓存局部性,可以手动将对象数据打包到单个字节数组中.


Pet*_*rey 1

在数据可以重用的情况下,第一种方法可能会导致数据重复。

但不是你提到的情况。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,不是为了性能而是为了清晰。