目前我正在改变我的心态,开发更多缓存友好的应用程序
在C ++中,我可以使用堆栈分配,也可以在一个数组(数据驱动编程)等中保存具有相同目的的数据...
但我也是Java开发人员,并且有一个问题:
我听说Java是“缓存未命中生成器”
堆中的所有内容,在分配或垃圾收集器工作后分散在整个RAM中。我认为同样的问题是C#。
以数据驱动方式编写Java是否有意义?
有没有办法优化Java代码,或者我们仍然坚持使用Java自动优化和缓存未命中?
答案 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
代替LinkedList
,HashMap
代替TreeMap
。
如果您需要更多控制权,请选择开放式寻址地图,特别是如果您正在处理原始类型(那里有一些不错的库)。
请注意其他线程对缓存执行的操作以及工作集大小的大小。有时,暂停并行线程进行相关工作是有好办法的,以确保它们不会在内存中分散太多,并且会破坏缓存。
与所有优化问题一样,基准测试经常(学会使用JMH)并使用数据来推动优化工作。