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

在设计java类时,有哪些建议可以实现CPU缓存友好性?

到目前为止我学到的是应该尽可能多地使用POD(即int而不是整数)。 因此,在分配包含对象时将连续分配数据。 例如

class Local { private int data0; private int data1; // ... }; 

比缓存更友好

 class NoSoLocal { private Integer data0; private Integer data1; //... }; 

后者将需要对Integer对象进行两次单独的分配,这些对象可以位于内存中的任意位置,尤其是。 GC运行后。 OTOH第一种方法可能会导致数据重复,以防数据可以重复使用。

有没有办法让它们在内存中彼此靠近,以便父对象及其’包含元素一次在CPU缓存中,而不是在整个内存中任意分布加GC将它们保持在一起?

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

但不是你提到的情况。 int是4个字节,引用通常是4个字节,因此使用Integer不会获得任何结果。 对于更复杂的类型,它可以产生很大的不同。

有没有办法让它们在内存中彼此靠近,以便父对象及其’包含元素一次在CPU缓存中,而不是在整个内存中任意分布加GC将它们保持在一起?

如果对象仅在一个地方使用,GC无论如何都会这样做。 如果对象在多个位置使用,则它们将接近一个引用。

注意:不能保证这种情况,但是在分配对象时,它们通常在内存中是连续的,因为这是最简单的分配策略。 复制保留的对象时,HotSpot GC将以与发现相反的顺序复制它们。 即他们仍在一起,但顺序相反。

注2:对于int使用4个字节仍然比对整数使用28个字节更有效(4个字节用于引用,16个字节用于对象头,4个字节用于值,4个字节用于填充)

注3:最重要的是,除非您已经测量了您的需求并且拥有更高性能的解决方案,否则您应该倾向于清晰度而不是性能。 在这种情况下, int不能为null,但integer可以为null。 如果你想要一个不应该为null的值,请使用int ,不是为了性能而是为了清晰。

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

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

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

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

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

     class Entry { long key; Object value; } Entry[] entries; 

    将被取代

     long[] keys; Object[] values; 
  3. 通过内联展平数据结构。 我最喜欢的例子是内联由byte[]表示的160位SHA1哈希。 之前的代码:

     class Blob { long offset; int length; byte[] sha1_hash; } 

    代码之后:

     class Blob { long offset; int length; int hash0, hash1, hash2, hash3, hash4; } 
  4. char[] 替换 String 。 你知道,Java中的String包含char[]对象。 为什么要为额外的参考支付性能损失?

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

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

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