拳击与创建1个元素的数组

时间:2017-08-09 13:19:22

标签: java garbage-collection jmh

免责声明:我读过Alexey Shipilev的this article,并了解纳米标记是一种邪恶。但无论如何我想自己试验和理解。

我试图测量数组创建与拳击byte。这是我的基准:

@Fork(1)
@Warmup(iterations = 5, timeUnit = TimeUnit.NANOSECONDS)
@Measurement(iterations = 5, timeUnit = TimeUnit.NANOSECONDS)
public class MyBenchmark {

    @Benchmark
    @OutputTimeUnit(TimeUnit.NANOSECONDS)
    @BenchmarkMode(Mode.AverageTime)
    public void arrayBenchmark(Blackhole bh) {
        byte[] b = new byte[1];
        b[0] = 20;
        bh.consume(b);
    }

    @Benchmark
    @OutputTimeUnit(TimeUnit.NANOSECONDS)
    @BenchmarkMode(Mode.AverageTime)
    public void bonxingBenchmark(Blackhole bh) {
        bh.consume(new Byte((byte) 20));
    }
}

我多次运行此基准测试,出于某种原因,我发现拳击比创建数组并将元素放入其中快1.5倍

所以我决定用-prof gc运行这个基准测试。结果如下:

MyBenchmark.arrayBenchmark                                     avgt    5     7.751 ±    0.537   ns/op
MyBenchmark.arrayBenchmark:·gc.alloc.rate                      avgt    5  1966.743 ±  143.624  MB/sec
MyBenchmark.arrayBenchmark:·gc.alloc.rate.norm                 avgt    5    24.000 ±    0.001    B/op
MyBenchmark.arrayBenchmark:·gc.churn.PS_Eden_Space             avgt    5  1966.231 ±  326.378  MB/sec
MyBenchmark.arrayBenchmark:·gc.churn.PS_Eden_Space.norm        avgt    5    23.999 ±    4.148    B/op
MyBenchmark.arrayBenchmark:·gc.churn.PS_Survivor_Space         avgt    5     0.042 ±    0.113  MB/sec
MyBenchmark.arrayBenchmark:·gc.churn.PS_Survivor_Space.norm    avgt    5     0.001 ±    0.001    B/op
MyBenchmark.arrayBenchmark:·gc.count                           avgt    5    37.000             counts
MyBenchmark.arrayBenchmark:·gc.time                            avgt    5    48.000                 ms

MyBenchmark.bonxingBenchmark                                   avgt    5     6.123 ±    1.306   ns/op
MyBenchmark.bonxingBenchmark:·gc.alloc.rate                    avgt    5  1664.504 ±  370.508  MB/sec
MyBenchmark.bonxingBenchmark:·gc.alloc.rate.norm               avgt    5    16.000 ±    0.001    B/op
MyBenchmark.bonxingBenchmark:·gc.churn.PS_Eden_Space           avgt    5  1644.547 ± 1004.476  MB/sec
MyBenchmark.bonxingBenchmark:·gc.churn.PS_Eden_Space.norm      avgt    5    15.769 ±    7.495    B/op
MyBenchmark.bonxingBenchmark:·gc.churn.PS_Survivor_Space       avgt    5     0.037 ±    0.067  MB/sec
MyBenchmark.bonxingBenchmark:·gc.churn.PS_Survivor_Space.norm  avgt    5    ≈ 10⁻³               B/op
MyBenchmark.bonxingBenchmark:·gc.count                         avgt    5    23.000             counts
MyBenchmark.bonxingBenchmark:·gc.time                          avgt    5    37.000                 ms

正如我们所看到的,GCarrayBenchmark案例中负载很重。分配率19661664gc-countgc-time也有所不同。 我认为原因 ,但不确定

现在我不太明白这种行为。我认为在我的情况下数组分配只意味着我们在某处分配1个字节。对我来说,它看起来与Boxing几乎相同,但实际上是不同的。

你能帮我理解吗?

什么是最重要的......我能相信这个基准吗?

1 个答案:

答案 0 :(得分:1)

TL; DR是因为Java对象memory layout *和数组开销。

每个对象都有header(存储了身份哈希码,锁和gc元信息)和类指针。此外,对象的地址应与8对齐。

假设您使用的是x86-64处理器,标头大小为8字节,类指针大小为4字节。 byte中的Byte字段占用1个字节=> Byte应该占用13个字节,但是舍入到对齐会给出恰好16个字节,您可以使用-prof gc观察到它。

对于数组计算大多是相同的,但是数组有4个字节length字段(从技术上讲,它不是真正的字段,但它并不重要),它给你8 + 4 + 4 = 16个字节对于数组,单字节元素为1字节,对齐为24字节。

所以其中一个原因是数组只是更大的对象。

第二个原因是更复杂的数组创建代码(您可以使用-prof perfasm-prof dtraceasm查看生成的代码)。数组初始化需要额外存储到length字段中,同样(我不知道为什么)JIT会生成四条prefetchnta指令。

所以是的,你可以部分地信任它:分配单个元素略微(虽然不是x1.5倍)慢。

*请注意,所有这些仅与Hotspot相关,其他JVM可能有不同的布局。