TL; DR:您不应将盲目的信任放入任何事物中。
首先,首先:在跳出实验结论之前,验证实验数据很重要。仅仅声称某些东西快3倍或慢3倍是奇怪的,因为您真的需要跟进性能差异的原因,而不仅仅是相信数字。这对于像您这样的纳米基准尤为重要。
其次,实验者应该清楚地了解他们控制什么,而不控制什么。在您的特定示例中,您正在从
@Benchmark方法中返回值,但是您可以合理地确定外部的调用者将对原始和引用执行相同的操作吗?如果您问自己这个问题,那么您将意识到您基本上正在测量测试基础结构。
到了重点。在我的机器(i5-4210U,Linux x86_64,JDK 8u40)上,测试得出:
Benchmark (value) Mode Samples Score Error Units...benchmarkReturnOrdinal 3 thrpt 5 0.876 ± 0.023 ops/ns...benchmarkReturnOrdinal 2 thrpt 5 0.876 ± 0.009 ops/ns...benchmarkReturnOrdinal 1 thrpt 5 0.832 ± 0.048 ops/ns...benchmarkReturnReference 3 thrpt 5 0.292 ± 0.006 ops/ns...benchmarkReturnReference 2 thrpt 5 0.286 ± 0.024 ops/ns...benchmarkReturnReference 1 thrpt 5 0.293 ± 0.008 ops/ns
好的,因此参考测试的速度要慢3倍。但是,等等,它使用了旧的JMH(1.1.1),让我们更新到最新的(1.7.1):
Benchmark (value) Mode Cnt Score Error Units...benchmarkReturnOrdinal 3 thrpt 5 0.326 ± 0.010 ops/ns...benchmarkReturnOrdinal 2 thrpt 5 0.329 ± 0.004 ops/ns...benchmarkReturnOrdinal 1 thrpt 5 0.329 ± 0.004 ops/ns...benchmarkReturnReference 3 thrpt 5 0.288 ± 0.005 ops/ns...benchmarkReturnReference 2 thrpt 5 0.288 ± 0.005 ops/ns...benchmarkReturnReference 1 thrpt 5 0.288 ± 0.002 ops/ns
糟糕,现在它们的速度才差一点。顺便说一句,这也告诉我们测试是受基础架构限制的。好吧,我们可以看看实际发生了什么吗?
如果建立基准测试,并仔细查看究竟调用了什么
@Benchmark方法,那么您将看到类似以下内容的信息:
public void benchmarkReturnOrdinal_thrpt_jmhStub(InfraControl control, RawResults result, ReturnEnumObjectVersusPrimitiveBenchmark_jmh l_returnenumobjectversusprimitivebenchmark0_0, Blackhole_jmh l_blackhole1_1) throws Throwable { long operations = 0; long realTime = 0; result.startTime = System.nanoTime(); do { l_blackhole1_1.consume(l_longname.benchmarkReturnOrdinal()); operations++; } while(!control.isDone); result.stopTime = System.nanoTime(); result.realTime = realTime; result.measuredOps = operations;}那
l_blackhole1_1有一个
consume“消耗”值的方法(请参阅
Blackhole参考资料)。
Blackhole.consume具有引用和基元的重载,仅此一项就足以证明性能差异。
这些方法为何看起来有所不同是有道理的:它们试图针对其参数类型尽可能快。即使我们尝试匹配它们,它们也不一定具有相同的性能特征,因此,更新的JMH的结果更加对称。现在,您甚至可以去
-profperfasm查看为测试生成的代码,并查看性能为何不同,但这超出了本文的重点。
如果您真的 想 了解返回原始图元和/或引用在性能方面有何不同,则需要输入细微的性能基准测试的一个 可怕的灰色区域 。例如这样的测试:
@BenchmarkMode(Mode.AverageTime)@OutputTimeUnit(TimeUnit.NANOSECONDS)@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)@Fork(5)public class PrimVsRef { @Benchmark public void prim() { doPrim(); } @Benchmark public void ref() { doRef(); } @CompilerControl(CompilerControl.Mode.DONT_INLINE) private int doPrim() { return 42; } @CompilerControl(CompilerControl.Mode.DONT_INLINE) private Object doRef() { return this; }}…这对于基元和引用产生相同的结果:
Benchmark Mode Cnt Score Error UnitsPrimVsRef.prim avgt 25 2.637 ± 0.017 ns/opPrimVsRef.ref avgt 25 2.634 ± 0.005 ns/op
如上所述,这些测试 需要 跟进结果的原因。在这种情况下,两者生成的代码几乎相同,因此可以解释结果。
prim:
[Verified Entry Point] 12.69% 1.81% 0x00007f5724aec100: mov %eax,-0x14000(%rsp) 0.90% 0.74% 0x00007f5724aec107: push %rbp 0.01% 0.01% 0x00007f5724aec108: sub $0x30,%rsp 12.23% 16.00% 0x00007f5724aec10c: mov $0x2a,%eax ; load "42" 0.95% 0.97% 0x00007f5724aec111: add $0x30,%rsp0.02% 0x00007f5724aec115: pop %rbp 37.94% 54.70% 0x00007f5724aec116: test %eax,0x10d1aee4(%rip) 0.04% 0.02% 0x00007f5724aec11c: retq
参考:
[Verified Entry Point] 13.52% 1.45% 0x00007f1887e66700: mov %eax,-0x14000(%rsp) 0.60% 0.37% 0x00007f1887e66707: push %rbp0.02% 0x00007f1887e66708: sub $0x30,%rsp 13.63% 16.91% 0x00007f1887e6670c: mov %rsi,%rax ; load "this" 0.50% 0.49% 0x00007f1887e6670f: add $0x30,%rsp 0.01% 0x00007f1887e66713: pop %rbp 39.18% 57.65% 0x00007f1887e66714: test %eax,0xe3e78e6(%rip) 0.02% 0x00007f1887e6671a: retq
[讽刺]看看有多容易![/讽刺]
模式是:问题越简单,您就需要做出更多的努力才能得出合理而可靠的答案。



