避免Java中的Cache未命中

时间:2015-06-02 14:06:18

标签: java caching garbage-collection

目前我正在改变我的心态,开发更多缓存友好的应用程序 在C ++中,我可以使用堆栈分配,也可以在一个数组(数据驱动编程)等中保存具有相同目的的数据...
但我也是Java开发人员,并且有一个问题:
我听说Java是“缓存未命中生成器” 堆中的所有内容,在分配或垃圾收集器工作后分散在整个RAM中。我认为同样的问题是C#。
以数据驱动方式编写Java是否有意义?
有没有办法优化Java代码,或者我们仍然坚持使用Java自动优化和缓存未命中?

4 个答案:

答案 0 :(得分:7)

  

在C ++中,我可以使用堆栈分配,也可以在一个数组(数据驱动编程)等中保存具有相同目的的数据......

在Java中,它将使用Escape Analysis自动在堆栈上放置短暂的对象。我不担心这个,除非你在分析器中看到这是一个问题。即使这样,分析器也可能阻止逃逸分析工作,这在实际程序中不是问题。

  

我听说Java是“缓存未命中生成器”。

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

  

堆中存在的所有内容,在分配或垃圾收集器工作后分散在整个RAM中。我认为同样的问题是C#。

Java(和C#)也不是随机内存编排器。理论上,物体可以在任何地方,但在实践中它们通常不是。考虑一下你是否有;

class A { }

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

如果您创建B,则A可以在任何地方,但通常不是。当Java在Eden空间中分配内存时,它通常在内存中是连续的。这是分配内存的最简单,最有效的方法。这意味着99.9%的时间A将紧跟在B之后,可能位于同一缓存行上。事实上,对于某些用例,“虚假共享”是Java中的一个真正问题。即当你想要两个不在同一缓存行上的对象时。

GC会发生什么?

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

  

以数据驱动方式编写Java是否有意义?

这种情况是这样的,并且在< 1%的情况可以产生很大的不同。但是,对于大多数代码而言,如果不是大多数应用程序,则需要担心的问题要多得多。

  

有没有办法优化Java代码,或者我们仍然坚持Java自动优化和缓存未命中?

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

  

我不想修改垃圾收集器。但我知道它会复制周围的所有东西,所以它会混淆一点结构。我想尽可能地避免这种情况。

这就是GC的作用。它将相关对象打包在一起,不仅仅是为了提高效率,而是因为以找到它们的方式复制对象是最简单的实现。如果你想要的话,随机排列数据是你必须要刻意做的事情,这将是更多的工作。例如如果你想避免“虚假分享”,这是非常重要的。

答案 1 :(得分:3)

您也可以在Java中提高缓存性能,但它也会涉及到。原始类型的数组是连续的内存块,因此只要你可以根据那些你是黄金的代码重写你的代码。正如斯捷潘诺夫所说,你可以用任何语言写FORTRAN。我已经看到这实际上是在过去完成的,但它不是很好 ......

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

答案 2 :(得分:2)

我的第一个建议是不要花费大量时间来担心它,除非你有特定的性能目标,你没有满足。此外,在编写高效的Java代码时还有很多其他途径需要探索。

...但是,如果你真的想沿着这条路走下去,你可以考虑&#34;轻量级对象&#34;模式(或轻量级):Wikipedia flyweight pattern

基元和基元类可能会占用连续存储,因此您可以将这些对象用于底层存储,并使用位于顶部的适配器类来返回数据的OO表示。

您必须小心不要分配给许多适配器。当一个适配器对象将引用传递给底层数据数组时,游标类型模式可能很有用。

答案 3 :(得分:0)

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

Java几乎没有关于内存布局的保证。你有两个有力的保证:

  • 超类的字段在内存中保存在一起(有助于避免错误共享而无需诉诸sun.misc.Contended
  • 数组在内存中是连续的

除了&#34;不要过于担心它之外,很难给出一般性的建议,但总的来说简单的策略效果最好。

尽可能使用ArrayList代替LinkedListHashMap代替TreeMap

如果您需要更多控制权,请选择开放式寻址地图,特别是如果您正在处理原始类型(那里有一些不错的库)。

请注意其他线程对缓存执行的操作以及工作集大小的大小。有时,暂停并行线程进行相关工作是有好办法的,以确保它们不会在内存中分散太多,并且会破坏缓存。

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