免责声明:我读过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
正如我们所看到的,GC
在arrayBenchmark
案例中负载很重。分配率1966
与1664
。 gc-count
和gc-time
也有所不同。 我认为原因 ,但不确定
现在我不太明白这种行为。我认为在我的情况下数组分配只意味着我们在某处分配1个字节。对我来说,它看起来与Boxing
几乎相同,但实际上是不同的。
你能帮我理解吗?
什么是最重要的......我能相信这个基准吗?
答案 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可能有不同的布局。