在 Java 中避免缓存未命中

Avoid Cache misses in Java

目前我的想法正在转变,以开发对缓存更友好的应用程序。
在 C++ 中,我尽可能地使用堆栈分配,我也在一个数组中保存具有相同目的的数据(数据驱动编程)等...
但我也是 Java 开发人员,有一个问题:
听说Java是"cache miss generator".
堆中的所有东西,在分配或垃圾收集器之后分散在整个 RAM 中 work.I 认为 C# 也有同样的问题。
以数据驱动的方式写 Java 有意义吗?
有什么方法可以优化 Java 代码,或者我们被 Java 自动优化和缓存未命中所困?

您也可以在 Java 中提高缓存性能,但这涉及到。基本类型的数组是连续的内存块,所以只要你能根据这些重写代码,你就是黄金。正如 Stepanov 所说,您可以用任何语言编写 FORTRAN。我已经看到过去确实这样做过,但这不是 nice...

另一方面,C# 在这方面是一种更友好的语言。 struct 类型具有连续成员,因此您可以在 C# 中构建更高级别的缓存友好抽象,另外 List<T> 分配了 value-type T在一个连续的内存块中。

我的第一个建议是不要花很多时间担心它,除非你有特定的性能目标你没有达到。此外,还有许多其他途径可以探索编写高效 Java 代码。

...但是,如果你真的想沿着这条路走下去,你可以考虑 "lightweight object" 模式(或享元):Wikipedia flyweight pattern.

数组和 类 原语可能会占用连续的存储空间,因此您可以将这些对象用于底层存储,并使用位于顶部的适配器 类 到 return 一个 OO数据的表示。

您必须注意不要分配给许多适配器。或许游标类型模式可能对将引用传递到基础数据数组的一个适配器对象有用。

In C++ im using stack allocation where i can,also i'm holding data with this same purpose in one array(Data Driven Programming) etc...

在Java 中,它会使用逃逸分析自动将短期活动对象放置在堆栈中。除非您在探查器中看到这是一个问题,否则我不会担心这个。即使那样,分析器也可能阻止逃逸分析工作,这在实际程序中不是问题。

I heard that Java is "cache miss generator".

Java 比 C++ 或 C# 代码具有更多的引用,这些代码是为使用嵌入在对象中的结构或对象而编写的。这有多大差异取决于您的应用程序对微调的敏感程度。

Everything there is in heap,and is scattered in whole RAM after allocation or garbage collector work.I think the same problem is with C#.

Java(和 C#)也不是随机内存排列器。理论上,这些对象可以在任何地方,但实际上它们通常不会。考虑一下你是否有;

class A { }

class B {
    A a = new A();
}

如果你创建一个 BA 可以在任何地方,但通常不是。 Java 在伊甸园space 分配内存时,通常在内存中是连续的。这是分配内存的最简单和最有效的方法。这意味着在 99.9% 的时间里,A 将紧跟在 B 之后,可能在同一缓存行上。事实上,"false sharing" 在某些用例中是 Java 中的一个真正问题。即当您想要两个不在同一缓存行上的对象时。

GC 会发生什么?

在 OpenJDK/Oracle JVM 中,对象的复制顺序与发现的顺序相反。即在大多数情况下,A 会出现在 B 之前。

Will it have sense to write Java in Data Driven way?

情况确实如此,在 < 1% 的情况下,这会产生很大的不同。但是,对于您的大部分代码,如果不是您的大部分应用程序,您需要担心的问题要大得多。

Is there any way to optimize Java code,or we are stuck with Java automatic optimization and cache misses?

您可以使用Unsafe 来控制您选择的内存结构。我们(Chronicle Software)拥有允许您这样做的库,但即使我们希望您使用我们的服务,但在 99% 的情况下,没有充分的理由担心这种微调。只有在极端情况下才会有真正的不同。

I dont want modify garbage collector.But i know it copies everything around so it messes a bit structure.I want avoid this as much as i can.

GC 就是这样做的。它将相关对象打包在一起,不仅是为了提高效率,而且因为以找到它们的方式复制对象是最简单的实现。随机排列数据是您必须故意做的事情,如果您愿意的话,这会带来更多工作。例如如果你想避免 "false sharing" 这是非常重要的。

您可以针对缓存未命中进行优化,但它涉及处理人为复杂的 class 层次结构、大量数组或走 sun.misc.Unsafe 道路,这会增加自己的开销。

Java 对内存布局提供的保证很少。你有两个强有力的保证:

  • superclass 的字段一起保存在内存中(可用于避免不求助于 sun.misc.Contended 的错误共享)
  • 数组在内存中是连续的

除了 "don't worry too much about it" 之外,很难给出一般性建议,但总的来说,简单的策略效果最好。

尽可能使用ArrayList而不是LinkedListHashMap而不是TreeMap

如果您需要更多控制,请使用开放寻址映射,特别是如果您处理原始类型(那里有一些不错的库)。

注意其他线程正在对您的缓存执行的操作以及您的工作集大小。有时暂停执行相关工作的并行线程是值得的,以确保它们不会在内存中偏离太多并破坏缓存。

与所有优化问题一样,经常进行基准测试(学习使用 JMH)并使用数据来推动您的优化工作。