ByteArrayOutputStream会发生什么样的JVM优化?

时间:2018-06-14 23:14:51

标签: java performance performance-testing benchmarking jmh

我有以下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优化?

1 个答案:

答案 0 :(得分:2)

假设你在5M操作/秒时有稳定的结果,这是可信的吗? 为了论证,让我们假设一个3GHz CPU(你可能在一台笔记本电脑上进行频率调整和增强驱动,但无论如何),5M ops / s =&gt;每个操作200ns => 600次循环。我们要求CPU做什么?

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

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

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

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

使用-prof gc,我可以看到这里的分配率是多少: ·gc.alloc.rate.norm: 360.000 B/op 所以,我猜测32 + 64 + 128 + 65 + change = 289b + change =&gt; change = 71b,这是一个变化,对吧?好吧,如果你考虑对象标题,那就不行了。我们有4个数组和一个对象=&gt; 5 * 12(压缩的oops标头)= 60,以及`ByteArrayOutputStream&#39;上的数组长度+计数字段因此,根据我的计算,改变应该是80b,但我可能错过了一些东西。底线是,对我们来说没有EscapeAnalysis。但有些CompressedOops有帮助。您可以使用分配探查器(如JVisualVM中的分析器)来跟踪此处的所有不同分配,或使用Java Mission Control中的采样分配探查器。

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

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

另一个正在进行的大规模优化是扩展堆以适应分配率。您可以尝试使用堆大小来查找它如何影响性能。

玩得开心: - )