Java方法直接调用与单元素循环调用
最近,我想看看直接在对象上调用方法与在同一对象上调用同一方法的性能差异是什么,如果将该对象添加到单个元素ArrayList中,并且我们尝试循环该元素列表 老实说,我的假设是会有微小的差异,因为我希望单元素循环被展开,因此调用模拟直接调用 为了测试我创建了以下简单的JMH示例: 令我惊讶的是,直接在元素上调用该方法似乎要快2,5倍。这是一个重要的数字,因为我的期望完全不同:Java方法直接调用与单元素循环调用,java,performance,jvm-hotspot,jmh,dtrace,Java,Performance,Jvm Hotspot,Jmh,Dtrace,最近,我想看看直接在对象上调用方法与在同一对象上调用同一方法的性能差异是什么,如果将该对象添加到单个元素ArrayList中,并且我们尝试循环该元素列表 老实说,我的假设是会有微小的差异,因为我希望单元素循环被展开,因此调用模拟直接调用 为了测试我创建了以下简单的JMH示例: 令我惊讶的是,直接在元素上调用该方法似乎要快2,5倍。这是一个重要的数字,因为我的期望完全不同: Benchmark
Benchmark Mode Cnt Score Error Units
SingleElementLoopBenchmark.directInvocation thrpt 10 332576972.176 ± 2740616.755 ops/s
SingleElementLoopBenchmark.singleElementLoopInvocation thrpt 10 130179329.978 ± 1303776.248 ops/s
我在推特上发了一点信息,希望得到一些提示,以了解为什么会发生这种情况。我被告知运行perf
会给我一个线索。因此,我使用-perf dtraceasm
运行了以下两个输出,第一个用于直接调用,第二个用于循环调用:
....[Hottest Region 1]..............................................................................
c2, com.benchmarks.loops.generated.SingleElementLoopBenchmark_directInvocation_jmhTest::directInvocation_thrpt_jmhStub, version 115 (69 bytes)
0x000000010b73c92c: mov %rbp,%r9
0x000000010b73c92f: movzbl 0x94(%r9),%r10d ;*getfield isDone {reexecute=0 rethrow=0 return_oop=0}
; - com.benchmarks.loops.generated.SingleElementLoopBenchmark_directInvocation_jmhTest::directInvocation_thrpt_jmhStub@27 (line 123)
; implicit exception: dispatches to 0x000000010b73ca5e
0x000000010b73c937: test %r10d,%r10d
0x000000010b73c93a: jne 0x000000010b73c9da ;*ifeq {reexecute=0 rethrow=0 return_oop=0}
; - com.benchmarks.loops.generated.SingleElementLoopBenchmark_directInvocation_jmhTest::directInvocation_thrpt_jmhStub@30 (line 123)
0x000000010b73c940: mov $0x1,%ebp
0x000000010b73c945: data16 data16 nopw 0x0(%rax,%rax,1) ;*aload_1 {reexecute=0 rethrow=0 return_oop=0}
; - com.benchmarks.loops.generated.SingleElementLoopBenchmark_directInvocation_jmhTest::directInvocation_thrpt_jmhStub@33 (line 124)
9.56% ↗ 0x000000010b73c950: mov 0x40(%rsp),%r10
1.00% │ 0x000000010b73c955: mov 0xc(%r10),%r10d ;*getfield dispatcher {reexecute=0 rethrow=0 return_oop=0}
│ ; - com.benchmarks.loops.SingleElementLoopBenchmark::directInvocation@1 (line 23)
│ ; - com.benchmarks.loops.generated.SingleElementLoopBenchmark_directInvocation_jmhTest::directInvocation_thrpt_jmhStub@17 (line 121)
0.17% │ 0x000000010b73c959: mov 0xc(%r12,%r10,8),%r11d ; implicit exception: dispatches to 0x000000010b73ca12
11.18% │ 0x000000010b73c95e: test %r11d,%r11d
0.00% │ 0x000000010b73c961: je 0x000000010b73c9c9 ;*invokevirtual performAction {reexecute=0 rethrow=0 return_oop=0}
│ ; - com.benchmarks.loops.SingleElementLoopBenchmark$Dispatcher::invoke@5 (line 40)
│ ; - com.benchmarks.loops.SingleElementLoopBenchmark::directInvocation@5 (line 23)
│ ; - com.benchmarks.loops.generated.SingleElementLoopBenchmark_directInvocation_jmhTest::directInvocation_thrpt_jmhStub@17 (line 121)
10.69% │ 0x000000010b73c963: mov %r9,(%rsp)
0.65% │ 0x000000010b73c967: mov 0x38(%rsp),%rsi
0.00% │ 0x000000010b73c96c: mov $0x1,%edx
0.14% │ 0x000000010b73c971: xchg %ax,%ax
10.08% │ 0x000000010b73c973: callq 0x000000010b6c2900 ; ImmutableOopMap{[48]=Oop [56]=Oop [64]=Oop [0]=Oop }
│ ;*invokevirtual consume {reexecute=0 rethrow=0 return_oop=0}
│ ; - com.benchmarks.loops.SingleElementLoopBenchmark$Listener::performAction@2 (line 53)
│ ; - com.benchmarks.loops.SingleElementLoopBenchmark$Dispatcher::invoke@5 (line 40)
│ ; - com.benchmarks.loops.SingleElementLoopBenchmark::directInvocation@5 (line 23)
│ ; - com.benchmarks.loops.generated.SingleElementLoopBenchmark_directInvocation_jmhTest::directInvocation_thrpt_jmhStub@17 (line 121)
│ ; {optimized virtual_call}
1.44% │ 0x000000010b73c978: mov (%rsp),%r9
0.19% │ 0x000000010b73c97c: movzbl 0x94(%r9),%r8d ;*ifeq {reexecute=0 rethrow=0 return_oop=0}
│ ; - com.benchmarks.loops.generated.SingleElementLoopBenchmark_directInvocation_jmhTest::directInvocation_thrpt_jmhStub@30 (line 123)
9.77% │ 0x000000010b73c984: mov 0x108(%r15),%r10
0.99% │ 0x000000010b73c98b: add $0x1,%rbp ; ImmutableOopMap{r9=Oop [48]=Oop [56]=Oop [64]=Oop }
│ ;*ifeq {reexecute=1 rethrow=0 return_oop=0}
│ ; - com.benchmarks.loops.generated.SingleElementLoopBenchmark_directInvocation_jmhTest::directInvocation_thrpt_jmhStub@30 (line 123)
0.02% │ 0x000000010b73c98f: test %eax,(%r10) ; {poll}
0.28% │ 0x000000010b73c992: test %r8d,%r8d
0.00% ╰ 0x000000010b73c995: je 0x000000010b73c950 ;*aload_1 {reexecute=0 rethrow=0 return_oop=0}
; - com.benchmarks.loops.generated.SingleElementLoopBenchmark_directInvocation_jmhTest::directInvocation_thrpt_jmhStub@33 (line 124)
0x000000010b73c997: movabs $0x10abd7d62,%r10
0x000000010b73c9a1: callq *%r10 ;*invokestatic nanoTime {reexecute=0 rethrow=0 return_oop=0}
; - com.benchmarks.loops.generated.SingleElementLoopBenchmark_directInvocation_jmhTest::directInvocation_thrpt_jmhStub@34 (line 124)
0x000000010b73c9a4: mov 0x30(%rsp),%r10
0x000000010b73c9a9: mov %rbp,0x18(%r10) ;*putfield measuredOps {reexecute=0 rethrow=0 return_oop=0}
; - com.benchmarks.loops.generated.SingleElementLoopBenchmark_directInvocation_jmhTest::directInvocation_thrpt_jmhStub@49 (line 126)
0x000000010b73c9ad: mov %rax,0x30(%r10) ;*putfield stopTime {reexecute=0 rethrow=0 return_oop=0}
; - com.benchmarks.loops.generated.SingleElementLoopBenchmark_directInvocation_jmhTest::directInvocation_thrpt_jmhStub@37 (line 124)
0x000000010b73c9b1: movq $0x0,0x20(%r10) ;*invokevirtual directInvocation {reexecute=0 rethrow=0 return_oop=0}
....................................................................................................
如果可能的话,我想要一些帮助来理解以上两个输出。据我所知,在循环时,程序似乎要付出相当大的代价来检查数组列表的大小,以便执行循环,并且由于列表泛型的原因,还需要将对象强制转换为适当的类型。我真的无法理解循环是否展开,但我相信它会展开吗
如果有人能理解上述输出并能给出一些指示,我们将不胜感激。您衡量的是一个几乎不做任何事情的操作,而不是一个至少涉及四个相关内存负载的操作:
- 数组列表大小
- ArrayList.elementData
- elementData.length
- elementData[0]
Listener
的实例,需要将对象强制转换为目标类型。这是一个额外的内存加载加上类型检查
从另一个角度看基准分数:单个元素列表的速度不是2.5倍,而是3纳秒:
Benchmark Mode Cnt Score Error Units
SingleElementLoopBenchmark.directInvocation avgt 5 2,340 ± 0,095 ns/op
SingleElementLoopBenchmark.singleElementLoopInvocation avgt 5 5,283 ± 0,074 ns/op
3纳秒!它只是2GHz内核的6个CPU周期。光可以在3纳秒内传播不到1米。您真的期望ArrayList的开销小于此值吗?无论开销有多低,如果与没有开销相比,这个比率可能会无限大。
vzeropper
在这里是疯狂的;循环中没有使用YMM AVX寄存器。(除了KNL,它在CPU上的速度并不慢,但更多的是表明JIT没有看透很多东西)。很明显,这里遗漏了大量优化。Andrei,首先感谢您的时间和详细解释。我在这里的主要目标是真正理解循环方法的开销,您已经详细解释了这一点。你说我可能从错误的角度看结果,这是有道理的。我只是试图权衡一个可能在短时间内导致过多调用的特定用例的利弊。再次感谢你。
Benchmark Mode Cnt Score Error Units
SingleElementLoopBenchmark.directInvocation avgt 5 2,340 ± 0,095 ns/op
SingleElementLoopBenchmark.singleElementLoopInvocation avgt 5 5,283 ± 0,074 ns/op