对于 ByteArrayOutputStream 进行什么样的 JVM 优化?



我有以下JMH基准测试(Java8(:

@Benchmark
public byte[] outputStream() {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (int i = 0; i < size; i++) {
baos.write(i);
}
return baos.toByteArray();
}

例如,当size == 65时,输出如下:

# Warmup Iteration   1: 3296444.108 ops/s
# Warmup Iteration   2: 2861235.712 ops/s
# Warmup Iteration   3: 4909462.444 ops/s
# Warmup Iteration   4: 4969418.622 ops/s
# Warmup Iteration   5: 5009353.033 ops/s
Iteration   1: 5006466.075 ops/sm 19s]
...

显然,在热身#2期间发生了一些事情,因此之后有巨大的加速。

我如何确定当时发生了哪种 JVM 优化?

假设您在 5M ops/s 下有一个稳定的结果,这可信吗? 为了便于讨论,让我们假设一个 3GHz CPU(您可能在一台具有频率缩放和睿频增强功能的笔记本电脑上,但无论如何(,5M ops/s => 每次操作 200ns => 600 个周期。我们要求 CPU 做什么?

  • 分配ByteArrayOutputStream,默认构造函数 ->新字节[32],+ 更改
  • 简单计数循环,65次,将一个字节写入数组
  • 调整字节数组的大小,2 倍。 32 -> 64 -> 128
  • 复制到新阵列 (65( 并返回
  • JMH 的简单循环核算

我们希望发生什么样的优化?

  • 从解释器到本地编译(Duh(
  • 循环
  • 展开和大量的循环优化,所有这些可能都没有多大帮助
  • ByteArrayOutputStream及其伙伴子午线的逃逸分析。我不认为它发生了。

我怎么知道发生了什么?我将使用一些有用的探查器运行它。JMH提供了大量的这些。

有了-prof gc我可以看到这里的分配率是多少: ·gc.alloc.rate.norm: 360.000 B/op 所以,我猜32 + 64 + 128 + 65 + change = 289b + change=>change = 71b,这是变化的分配,对吧?好吧,如果您考虑对象标头,则不会。我们有 4 个数组和一个对象 => 5 * 12(压缩的 oops 标头(= 60,并且"ByteArrayOutputStream"上的数组长度 + count 字段 = 20。所以根据我的计算,变化应该是 80b,但我可能错过了一些东西。底线是,我们没有逃避分析。但是一些压缩的哎呀会有所帮助。您可以使用分配探查器(如 JVisualVM 中的探查器(来跟踪此处的所有不同分配,也可以使用采样分配探查器(如 Java Mission Control 中的探查器(。

您可以使用-prof perfasm查看该级别的程序集输出和配置文件。这是一个很长的练习,所以我不打算在这里讨论它。您可以看到的一个很酷的优化是,JVM 不会将其在方法结束时创建的新数组副本清零。您还可以看到,数组的分配和复制是按预期花费时间的地方。

最后,这里要进行的明显优化只是 JIT 编译。您可以使用 JITWatch 等工具探索每个级别的编译所执行的操作。您可以使用命令行标志来查找每个编译级别的性能(-jvmArgs=-Xint' to run in the interpreter-XX:TieredStopAtLevel=1' 停止在 C1(。

另一个大规模的优化是扩展堆以适应分配率。您可以尝试堆大小,以了解这对性能的影响。

玩得开心:-(

最新更新