在什么时候值得在Java中重用数组?

Java中的缓冲区需要多大才能值得重用?

或者,换句话说:我可以重复分配,使用和丢弃byte[]对象,或者运行一个池来保留和重用它们。我可能会分配很多经常被丢弃的小缓冲区,或者一些不被丢弃的大缓冲区。在什么规模上将它们集中起来比重新分配更便宜,小的分配与大的分配相比如何?

编辑:

好的,具体参数。假设英特尔酷睿2双核CPU,最新的VM版本,用于操作系统选择。这个问题并不像听起来那么模糊...一点代码和一个图表就可以回答它。

编辑2:

你已经发布了很多好的一般规则和讨论,但这个问题确实需要数字。发布他们(和代码)!理论是伟大的,但证据是数字。结果是否因系统而异并不重要,我只是在寻找一个粗略的估计(数量级)。似乎没有人知道性能差异是否会是1.1,2,10或100 +的因素,这是重要的事情。对于任何使用大型数组的Java代码来说,这都很重要 - 网络,生物信息学等。

获得良好基准的建议:

  1. 在基准测试中运行代码之前预热代码。所有方法都应至少调用 1000 10000 次,以获得完整的 JIT 优化。
  2. 确保基准测试方法至少运行 1 10 秒,并尽可能使用 System.nanotime 来获得准确的计时。
  3. 在仅运行最少应用程序的系统上运行基准测试
  4. 运行基准测试 3-5 次并报告所有时间,因此我们可以看到它的一致性。

我知道这是一个模糊且有些苛刻的问题。我会定期检查这个问题,答案会得到评论并一致地评级。懒惰的答案不会(请参阅下面的标准)。如果我没有任何彻底的答案,我会附上赏金。无论如何,我可能会奖励一个非常好的答案,并增加一点。

我所知道的(不需要重复):

  • Java 内存分配和 GC 速度更快,而且速度越来越快。
  • 对象池曾经是一个很好的优化,但现在它大部分时间都会损害性能。
  • 对象池“通常不是一个好主意,除非对象的创建成本很高。Yadda yadda.

我不知道的是:

  • 我希望内存分配在标准现代 CPU 上运行的速度 (MB/s) 有多快?
  • 分配大小如何影响分配率?
  • 在池中,分配的数量/大小与重用的收支平衡点是什么?

路由到已接受的答案(越多越好):

  • 最近的白皮书显示了现代CPU上的分配和GC数据(最近一年左右,JVM 1.6或更高版本)
  • 代码为简洁和正确的微基准测试,我可以运行
  • 解释分配如何影响绩效以及影响原因
  • 测试这种优化的真实示例/轶事

背景:

我正在开发一个库,为Java添加LZF压缩支持。此库通过添加其他压缩级别(更多压缩)以及与 C LZF 库中的字节流的兼容性来扩展 H2 DBMS LZF 类。我正在考虑的一件事是,是否值得尝试重用用于压缩/解压缩流的固定大小的缓冲区。缓冲区可能是 ~8 kB 或 ~32 kB,在原始版本中,它们约为 128 kB。可以为每个流分配一次或多次缓冲区。我正在尝试弄清楚如何处理缓冲区以获得最佳性能,并着眼于未来潜在的多线程。

是的,如果有人有兴趣使用它,该库将作为开源发布。


答案 1

如果你想要一个简单的答案,那就是没有简单的答案。再多的“懒惰”来回答(以及暗示人们)都无济于事。

我希望内存分配在标准现代 CPU 上运行的速度 (MB/s) 有多快?

以 JVM 可以为零内存的速度,假设分配不会触发垃圾回收。如果它确实触发了垃圾回收,则在不知道使用什么 GC 算法、堆大小和其他参数,以及在应用程序的生存期内对应用程序的非垃圾对象工作集的分析,就不可能进行预测。

分配大小如何影响分配率?

见上文。

在池中,分配的数量/大小与重用的收支平衡点是什么?

如果你想要一个简单的答案,那就是没有简单的答案。

黄金法则是,堆越大(最多可用物理内存量),垃圾对象的摊销成本就越小。使用快速复制垃圾回收器,随着堆变大,释放垃圾对象的摊销成本接近于零。GC的成本实际上是由(简单来说)GC必须处理的非垃圾对象的数量和大小决定的。

假设堆很大,则在分配和GC大型对象(在一个GC周期内)的生命周期成本接近分配对象时将内存归零的成本。

编辑:如果您想要的只是一些简单的数字,请编写一个简单的应用程序来分配和丢弃大型缓冲区,并使用各种GC和堆参数在计算机上运行它,看看会发生什么。但请注意,这不会给你一个现实的答案,因为实际的GC成本取决于应用程序的非垃圾对象。

我不打算为你写一个基准,因为我知道它会给你虚假的答案。

编辑2:回应OP的评论。

因此,我应该期望分配的运行速度与System.arraycopy一样快,或者完全JITed数组初始化循环(在我的最后一个工作台上大约1GB / s,但我对结果持怀疑态度)?

理论上是的。在实践中,很难以将分配成本与GC成本分开的方式进行测量。

按堆大小,您是说为 JVM 使用分配更大的内存量实际上会降低性能吗?

不,我是说它可能会提高性能。显著。(前提是您不会遇到操作系统级别的虚拟内存效应。

分配仅适用于数组,我的代码中的几乎所有其他内容都在堆栈上运行。它应该简化性能的测量和预测。

或。坦率地说,我认为你不会通过回收缓冲液得到太大的改善。

但是,如果您打算沿着这条路走下去,请创建一个包含两个实现的缓冲池接口。第一个是回收缓冲区的真正线程安全缓冲池。第二个是虚拟池,它每次调用时都会简单地分配一个新的缓冲区,并将其视为 no-op。最后,允许应用程序开发人员通过方法和/或构造函数参数和/或运行时配置属性在池实现之间进行选择。应用程序还应该能够提供自己创建的缓冲池类/实例。allocdisposesetBufferPool


答案 2

当它比年轻空间大时。

如果数组大于线程局部幼空间,则直接将其分配到旧空间中。旧空间的垃圾收集比年轻空间慢得多。因此,如果您的数组大于年轻空间,那么重用它可能是有意义的。

在我的机器上,32kb超过了年轻的空间。因此,重用它是有意义的。


推荐