为纯方法重新计算 JVM JIT 方法
JVM JIT method recalculate for pure methods
使用 jmh 对以下 Java 代码进行基准测试:
interface MyInterface {
public int test(int i);
}
class A implements MyInterface {
public int test(int i) {
return (int)Math.sin(Math.cos(i));
}
}
@State(Scope.Thread)
public class MyBenchmark {
public MyInterface inter;
@Setup(Level.Trial)
public void init() {
inter = new A();
}
@Benchmark
public void testMethod(Blackhole sink) {
int[] res = new int[2];
res[0] = inter.test(1);
res[1] = inter.test(1);
sink.consume(res);
}
}
使用 mvn package && java -XX:-UseCompressedOops -XX:CompileCommand='print, *.testMethod' -jar target/benchmarks.jar -wi 10 -i 1 -f 1
,我能够得到程序集,如果我们关注 C2 中的那个(如下所示),我们可以看到 cos
和 sin
被调用了两次。
ImmutableOopMap{}pc offsets: 796 812 828 Compiled method (c2) 402 563 4 org.sample.MyBenchmark::testMethod (42 bytes)
total in heap [0x00007efd3d74fb90,0x00007efd3d7503a0] = 2064
relocation [0x00007efd3d74fcd0,0x00007efd3d74fd08] = 56
constants [0x00007efd3d74fd20,0x00007efd3d74fd40] = 32
main code [0x00007efd3d74fd40,0x00007efd3d750040] = 768
stub code [0x00007efd3d750040,0x00007efd3d750068] = 40
oops [0x00007efd3d750068,0x00007efd3d750070] = 8
metadata [0x00007efd3d750070,0x00007efd3d750080] = 16
scopes data [0x00007efd3d750080,0x00007efd3d750108] = 136
scopes pcs [0x00007efd3d750108,0x00007efd3d750358] = 592
dependencies [0x00007efd3d750358,0x00007efd3d750360] = 8
handler table [0x00007efd3d750360,0x00007efd3d750390] = 48
nul chk table [0x00007efd3d750390,0x00007efd3d7503a0] = 16
----------------------------------------------------------------------
org/sample/MyBenchmark.testMethod(Lorg/openjdk/jmh/infra/Blackhole;)V [0x00007efd3d74fd40, 0x00007efd3d750068] 808 bytes
[Constants]
0x00007efd3d74fd20 (offset: 0): 0x00000000 0x3ff0000000000000
0x00007efd3d74fd24 (offset: 4): 0x3ff00000
0x00007efd3d74fd28 (offset: 8): 0xf4f4f4f4 0xf4f4f4f4f4f4f4f4
0x00007efd3d74fd2c (offset: 12): 0xf4f4f4f4
0x00007efd3d74fd30 (offset: 16): 0xf4f4f4f4 0xf4f4f4f4f4f4f4f4
0x00007efd3d74fd34 (offset: 20): 0xf4f4f4f4
0x00007efd3d74fd38 (offset: 24): 0xf4f4f4f4 0xf4f4f4f4f4f4f4f4
0x00007efd3d74fd3c (offset: 28): 0xf4f4f4f4
Argument 0 is unknown.RIP: 0x7efd3d74fd40 Code size: 0x00000328
[Entry Point]
# {method} {0x00007efd35857f08} 'testMethod' '(Lorg/openjdk/jmh/infra/Blackhole;)V' in 'org/sample/MyBenchmark'
# this: rsi:rsi = 'org/sample/MyBenchmark'
# parm0: rdx:rdx = 'org/openjdk/jmh/infra/Blackhole'
# [sp+0x30] (sp of caller)
0x00007efd3d74fd40: cmp 0x8(%rsi),%rax ; {no_reloc}
0x00007efd3d74fd44: jne 0x7efd35c99c60 ; {runtime_call ic_miss_stub}
0x00007efd3d74fd4a: nop
0x00007efd3d74fd4c: nopl 0x0(%rax)
[Verified Entry Point]
0x00007efd3d74fd50: mov %eax,0xfffffffffffec000(%rsp)
0x00007efd3d74fd57: push %rbp
0x00007efd3d74fd58: sub [=11=]x20,%rsp ;*synchronization entry
; - org.sample.MyBenchmark::testMethod@-1 (line 64)
0x00007efd3d74fd5c: mov %rdx,(%rsp)
0x00007efd3d74fd60: mov %rsi,%rbp
0x00007efd3d74fd63: mov 0x60(%r15),%rbx
0x00007efd3d74fd67: mov %rbx,%r10
0x00007efd3d74fd6a: add [=11=]x1a8,%r10
0x00007efd3d74fd71: cmp 0x70(%r15),%r10
0x00007efd3d74fd75: jnb 0x7efd3d74ffcc
0x00007efd3d74fd7b: mov %r10,0x60(%r15)
0x00007efd3d74fd7f: prefetchnta 0xc0(%r10)
0x00007efd3d74fd87: movq [=11=]x1,(%rbx)
0x00007efd3d74fd8e: prefetchnta 0x100(%r10)
0x00007efd3d74fd96: mov %rbx,%rdi
0x00007efd3d74fd99: add [=11=]x18,%rdi
0x00007efd3d74fd9d: prefetchnta 0x140(%r10)
0x00007efd3d74fda5: prefetchnta 0x180(%r10)
0x00007efd3d74fdad: movabs [=11=]x7efd350d9b38,%r10 ; {metadata({type array int})}
0x00007efd3d74fdb7: mov %r10,0x8(%rbx)
0x00007efd3d74fdbb: movl [=11=]x64,0x10(%rbx)
0x00007efd3d74fdc2: mov [=11=]x32,%ecx
0x00007efd3d74fdc7: xor %rax,%rax
0x00007efd3d74fdca: shl [=11=]x3,%rcx
0x00007efd3d74fdce: rep stosb (%rdi) ;*newarray {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@4 (line 65)
0x00007efd3d74fdd1: mov 0x10(%rbp),%r10 ;*getfield inter {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@20 (line 67)
0x00007efd3d74fdd5: mov 0x8(%r10),%r10 ; implicit exception: dispatches to 0x00007efd3d74fffd
0x00007efd3d74fdd9: movabs [=11=]x7efd3587f8c8,%r11 ; {metadata('org/sample/A')}
0x00007efd3d74fde3: cmp %r11,%r10
0x00007efd3d74fde6: jne 0x7efd3d74fffd ;*synchronization entry
; - org.sample.A::test@-1 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fdec: vmovsd 0xffffff2c(%rip),%xmm0 ; {section_word}
0x00007efd3d74fdf4: vmovq %xmm0,%r13
0x00007efd3d74fdf9: movabs [=11=]x7efd35c53b33,%r10
0x00007efd3d74fe03: callq %r10 ;*invokestatic cos {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@2 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fe06: movabs [=11=]x7efd35c5349c,%r10
0x00007efd3d74fe10: callq %r10 ;*invokestatic sin {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@5 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fe13: vcvttsd2si %xmm0,%r11d
0x00007efd3d74fe17: cmp [=11=]x80000000,%r11d
0x00007efd3d74fe1e: jne 0x7efd3d74fe30
0x00007efd3d74fe20: sub [=11=]x8,%rsp
0x00007efd3d74fe24: vmovsd %xmm0,(%rsp)
0x00007efd3d74fe29: callq 0x7efd35ca745b ; {runtime_call StubRoutines (2)}
0x00007efd3d74fe2e: pop %r11
0x00007efd3d74fe30: mov %r11d,0x18(%rbx) ;*iastore {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@29 (line 67)
0x00007efd3d74fe34: mov [=11=]x1,%ebp
0x00007efd3d74fe39: jmp 0x7efd3d74fe43
0x00007efd3d74fe3b: nopl 0x0(%rax,%rax)
0x00007efd3d74fe40: mov %r11d,%ebp ;*synchronization entry
; - org.sample.A::test@-1 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fe43: vmovq %r13,%xmm0
0x00007efd3d74fe48: movabs [=11=]x7efd35c53b33,%r10
0x00007efd3d74fe52: callq %r10 ;*invokestatic cos {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@2 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fe55: movabs [=11=]x7efd35c5349c,%r10
0x00007efd3d74fe5f: callq %r10 ;*invokestatic sin {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@5 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fe62: vcvttsd2si %xmm0,%r11d
0x00007efd3d74fe66: cmp [=11=]x80000000,%r11d
0x00007efd3d74fe6d: jne 0x7efd3d74fe7f
0x00007efd3d74fe6f: sub [=11=]x8,%rsp
0x00007efd3d74fe73: vmovsd %xmm0,(%rsp)
0x00007efd3d74fe78: callq 0x7efd35ca745b ; {runtime_call StubRoutines (2)}
0x00007efd3d74fe7d: pop %r11
0x00007efd3d74fe7f: mov %r11d,0x18(%rbx,%rbp,4) ;*synchronization entry
; - org.sample.A::test@-1 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fe84: vmovq %r13,%xmm0
0x00007efd3d74fe89: movabs [=11=]x7efd35c53b33,%r10
0x00007efd3d74fe93: callq %r10 ;*invokestatic cos {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@2 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fe96: movabs [=11=]x7efd35c5349c,%r10
0x00007efd3d74fea0: callq %r10 ;*invokestatic sin {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@5 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fea3: vcvttsd2si %xmm0,%r11d
0x00007efd3d74fea7: cmp [=11=]x80000000,%r11d
0x00007efd3d74feae: jne 0x7efd3d74fec0
0x00007efd3d74feb0: sub [=11=]x8,%rsp
0x00007efd3d74feb4: vmovsd %xmm0,(%rsp)
0x00007efd3d74feb9: callq 0x7efd35ca745b ; {runtime_call StubRoutines (2)}
0x00007efd3d74febe: pop %r11
0x00007efd3d74fec0: mov %r11d,0x1c(%rbx,%rbp,4) ;*synchronization entry
; - org.sample.A::test@-1 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fec5: vmovq %r13,%xmm0
0x00007efd3d74feca: movabs [=11=]x7efd35c53b33,%r10
0x00007efd3d74fed4: callq %r10 ;*invokestatic cos {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@2 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fed7: movabs [=11=]x7efd35c5349c,%r10
0x00007efd3d74fee1: callq %r10 ;*invokestatic sin {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@5 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fee4: vcvttsd2si %xmm0,%r11d
0x00007efd3d74fee8: cmp [=11=]x80000000,%r11d
0x00007efd3d74feef: jne 0x7efd3d74ff01
0x00007efd3d74fef1: sub [=11=]x8,%rsp
0x00007efd3d74fef5: vmovsd %xmm0,(%rsp)
0x00007efd3d74fefa: callq 0x7efd35ca745b ; {runtime_call StubRoutines (2)}
0x00007efd3d74feff: pop %r11
0x00007efd3d74ff01: mov %r11d,0x20(%rbx,%rbp,4) ;*synchronization entry
; - org.sample.A::test@-1 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74ff06: vmovq %r13,%xmm0
0x00007efd3d74ff0b: movabs [=11=]x7efd35c53b33,%r10
0x00007efd3d74ff15: callq %r10 ;*invokestatic cos {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@2 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74ff18: movabs [=11=]x7efd35c5349c,%r10
0x00007efd3d74ff22: callq %r10 ;*invokestatic sin {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@5 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74ff25: vcvttsd2si %xmm0,%r11d
0x00007efd3d74ff29: cmp [=11=]x80000000,%r11d
0x00007efd3d74ff30: jne 0x7efd3d74ff42
0x00007efd3d74ff32: sub [=11=]x8,%rsp
0x00007efd3d74ff36: vmovsd %xmm0,(%rsp)
0x00007efd3d74ff3b: callq 0x7efd35ca745b ; {runtime_call StubRoutines (2)}
0x00007efd3d74ff40: pop %r11
0x00007efd3d74ff42: mov %r11d,0x24(%rbx,%rbp,4) ;*iastore {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@29 (line 67)
0x00007efd3d74ff47: mov %ebp,%r11d
0x00007efd3d74ff4a: add [=11=]x4,%r11d ;*iinc {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@30 (line 66)
0x00007efd3d74ff4e: cmp [=11=]x61,%r11d
0x00007efd3d74ff52: jl 0x7efd3d74fe40 ;*if_icmpge {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@13 (line 66)
0x00007efd3d74ff58: cmp [=11=]x64,%r11d
0x00007efd3d74ff5c: jnl 0x7efd3d74ffac
0x00007efd3d74ff5e: add [=11=]x4,%ebp ;*iinc {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@30 (line 66)
0x00007efd3d74ff61: nop ;*synchronization entry
; - org.sample.A::test@-1 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74ff64: vmovq %r13,%xmm0
0x00007efd3d74ff69: movabs [=11=]x7efd35c53b33,%r10
0x00007efd3d74ff73: callq %r10 ;*invokestatic cos {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@2 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74ff76: movabs [=11=]x7efd35c5349c,%r10
0x00007efd3d74ff80: callq %r10 ;*invokestatic sin {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@5 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74ff83: vcvttsd2si %xmm0,%r10d
0x00007efd3d74ff87: cmp [=11=]x80000000,%r10d
0x00007efd3d74ff8e: jne 0x7efd3d74ffa0
0x00007efd3d74ff90: sub [=11=]x8,%rsp
0x00007efd3d74ff94: vmovsd %xmm0,(%rsp)
0x00007efd3d74ff99: callq 0x7efd35ca745b ; {runtime_call StubRoutines (2)}
0x00007efd3d74ff9e: pop %r10
0x00007efd3d74ffa0: mov %r10d,0x18(%rbx,%rbp,4) ;*iastore {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@29 (line 67)
0x00007efd3d74ffa5: incl %ebp ;*iinc {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@30 (line 66)
0x00007efd3d74ffa7: cmp [=11=]x64,%ebp
0x00007efd3d74ffaa: jl 0x7efd3d74ff64
0x00007efd3d74ffac: mov (%rsp),%rsi
0x00007efd3d74ffb0: test %rsi,%rsi
0x00007efd3d74ffb3: je 0x7efd3d74ffe8 ;*if_icmpge {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@13 (line 66)
0x00007efd3d74ffb5: mov %rbx,%rdx
0x00007efd3d74ffb8: nop
0x00007efd3d74ffbb: callq 0x7efd362c50e0 ; ImmutableOopMap{}
;*invokevirtual consume {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@38 (line 69)
; {optimized virtual_call}
0x00007efd3d74ffc0: add [=11=]x20,%rsp
0x00007efd3d74ffc4: pop %rbp
0x00007efd3d74ffc5: test %eax,0x18f98035(%rip) ; {poll_return}
0x00007efd3d74ffcb: retq
0x00007efd3d74ffcc: mov [=11=]x64,%edx
0x00007efd3d74ffd1: movabs [=11=]x7efd350d9b38,%rsi ; {metadata({type array int})}
0x00007efd3d74ffdb: callq 0x7efd35d5fd60 ; ImmutableOopMap{rbp=Oop [0]=Oop }
;*newarray {reexecute=0 rethrow=0 return_oop=1}
; - org.sample.MyBenchmark::testMethod@4 (line 65)
; {runtime_call _new_array_Java}
0x00007efd3d74ffe0: mov %rax,%rbx
0x00007efd3d74ffe3: jmpq 0x7efd3d74fdd1
0x00007efd3d74ffe8: mov [=11=]xfffffff6,%esi
0x00007efd3d74ffed: mov %rbx,%rbp
0x00007efd3d74fff0: nop
0x00007efd3d74fff3: callq 0x7efd35c9b560 ; ImmutableOopMap{rbp=Oop }
;*invokevirtual consume {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@38 (line 69)
; {runtime_call UncommonTrapBlob}
0x00007efd3d74fff8: callq 0x7efd55167aa0 ; {runtime_call}
0x00007efd3d74fffd: mov [=11=]xffffff86,%esi
0x00007efd3d750002: mov %rbx,0x8(%rsp)
0x00007efd3d750007: callq 0x7efd35c9b560 ; ImmutableOopMap{rbp=Oop [0]=Oop [8]=Oop }
;*aload_3 {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@16 (line 67)
; {runtime_call UncommonTrapBlob}
0x00007efd3d75000c: callq 0x7efd55167aa0 ;*newarray {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@4 (line 65)
; {runtime_call}
0x00007efd3d750011: mov %rax,%rsi
0x00007efd3d750014: jmp 0x7efd3d750019
0x00007efd3d750016: mov %rax,%rsi ;*invokevirtual consume {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@38 (line 69)
0x00007efd3d750019: add [=11=]x20,%rsp
0x00007efd3d75001d: pop %rbp
0x00007efd3d75001e: jmpq 0x7efd35d64160 ; {runtime_call _rethrow_Java}
0x00007efd3d750023: hlt
0x00007efd3d750024: hlt
0x00007efd3d750025: hlt
0x00007efd3d750026: hlt
0x00007efd3d750027: hlt
0x00007efd3d750028: hlt
0x00007efd3d750029: hlt
0x00007efd3d75002a: hlt
0x00007efd3d75002b: hlt
0x00007efd3d75002c: hlt
0x00007efd3d75002d: hlt
0x00007efd3d75002e: hlt
0x00007efd3d75002f: hlt
0x00007efd3d750030: hlt
0x00007efd3d750031: hlt
0x00007efd3d750032: hlt
0x00007efd3d750033: hlt
0x00007efd3d750034: hlt
0x00007efd3d750035: hlt
0x00007efd3d750036: hlt
0x00007efd3d750037: hlt
0x00007efd3d750038: hlt
0x00007efd3d750039: hlt
0x00007efd3d75003a: hlt
0x00007efd3d75003b: hlt
0x00007efd3d75003c: hlt
0x00007efd3d75003d: hlt
0x00007efd3d75003e: hlt
0x00007efd3d75003f: hlt
我期待 inter.test
的结果被缓存或其他东西,以便 inter.test
(sin 和 cos)只被调用一次。我可以使用任何选项来使 JVM (JIT) 这样做吗?或者是什么阻止了 JVM (JIT) 看到该方法是纯方法?
环境:
$ java -version
openjdk version "9-internal"
OpenJDK Runtime Environment (build 9-internal+0-2016-04-14-195246.buildd.src)
OpenJDK 64-Bit Server VM (build 9-internal+0-2016-04-14-195246.buildd.src, mixed mode)
# jmh version
<jmh.version>1.19</jmh.version>
据我所知, 对 纯 方法的冗余调用(即调用具有相同参数的纯方法),除了通过内联间接调用。
也就是说,如果对纯方法的冗余调用都在调用点内联,则通过 CSE 和 GVN 等常规优化间接在内联代码中检测到冗余,这样额外的成本来电通常会消失。但是,如果方法未内联,我认为 JVM 不会将它们标记为 "pure",因此无法消除它们(与 many native compilers which can 不同)。
仍然考虑到内联 可以 删除冗余调用,问题仍然存在:为什么冗余 Math.sin
和 Math.cos
调用没有内联并最终优化走了?
事实证明,Math.sin
和 Math.cos
与其他几个 Math
和 JDK 中的其他方法一样被特殊处理为 intrinsic functions。下面您将详细了解 Java 8 和 Java 9 的一些版本中发生的情况。您展示的反汇编来自 Java 9 的更高版本,它以不同的方式处理这个问题,覆盖在最后。
在 JVM 中处理触发方法的方式是...复杂。原则上,Math.sin
和 Math.cos
在 x86 上使用本机 FP 指令内联为内部方法,但有一些注意事项。
您的基准测试中有很多无关因素使其更难分析,例如数组分配、对 [=23= 的调用]、Math.sin
和 [=17 的使用=]、传递常量(这可能会导致某些触发指令被完全优化)、接口的使用 A
和该接口的实现等
取而代之的是,让我们去掉那个垃圾并将其简化为一个更简单的版本,它只使用相同的参数调用 Math.sin(x)
三次,并且 returns 总和:
private double i = Math.PI / 4 - 0.01;
@Benchmark
public double testMethod() {
double res0 = Math.sin(i);
double res1 = Math.sin(i);
double res2 = Math.sin(i);
return res0 + res1 + res2;
}
运行 这与 JHM args -bm avgt -tu ns -wi 5 -f 1 -i 5
我得到大约 40 ns/op,这是在现代 x86 上的单个 fsin
调用范围的下限硬件。让我们来看看汇编:
[Constants]
0x00007ff2e4dbbd20 (offset: 0): 0x54442d18 0x3fe921fb54442d18
0x00007ff2e4dbbd24 (offset: 4): 0x3fe921fb
0x00007ff2e4dbbd28 (offset: 8): 0xf4f4f4f4 0xf4f4f4f4f4f4f4f4
0x00007ff2e4dbbd2c (offset: 12): 0xf4f4f4f4
0x00007ff2e4dbbd30 (offset: 16): 0xf4f4f4f4 0xf4f4f4f4f4f4f4f4
0x00007ff2e4dbbd34 (offset: 20): 0xf4f4f4f4
0x00007ff2e4dbbd38 (offset: 24): 0xf4f4f4f4 0xf4f4f4f4f4f4f4f4
0x00007ff2e4dbbd3c (offset: 28): 0xf4f4f4f4
(snip)
[Verified Entry Point]
0x00007ff2e4dbbd50: sub [=11=]x28,%rsp
0x00007ff2e4dbbd57: mov %rbp,0x20(%rsp) ;*synchronization entry
; - Whosebug.TrigBench::testMethod@-1 (line 38)
0x00007ff2e4dbbd5c: vmovsd 0x10(%rsi),%xmm2 ;*getfield i
; - Whosebug.TrigBench::testMethod@1 (line 38)
0x00007ff2e4dbbd61: vmovapd %xmm2,%xmm1
0x00007ff2e4dbbd65: sub [=11=]x8,%rsp
0x00007ff2e4dbbd69: vmovsd %xmm1,(%rsp)
0x00007ff2e4dbbd6e: fldl (%rsp)
0x00007ff2e4dbbd71: fsin
0x00007ff2e4dbbd73: fstpl (%rsp)
0x00007ff2e4dbbd76: vmovsd (%rsp),%xmm1
0x00007ff2e4dbbd7b: add [=11=]x8,%rsp ;*invokestatic sin
; - Whosebug.TrigBench::testMethod@20 (line 40)
0x00007ff2e4dbbd7f: vmovsd 0xffffff99(%rip),%xmm3 ; {section_word}
0x00007ff2e4dbbd87: vandpd 0xffe68411(%rip),%xmm2,%xmm0
; {external_word}
0x00007ff2e4dbbd8f: vucomisd %xmm0,%xmm3
0x00007ff2e4dbbd93: jnb 0x7ff2e4dbbe4c
0x00007ff2e4dbbd99: vmovq %xmm3,%r13
0x00007ff2e4dbbd9e: vmovq %xmm1,%rbp
0x00007ff2e4dbbda3: vmovq %xmm2,%rbx
0x00007ff2e4dbbda8: vmovapd %xmm2,%xmm0
0x00007ff2e4dbbdac: movabs [=11=]x7ff2f9abaeec,%r10
0x00007ff2e4dbbdb6: callq %r10
0x00007ff2e4dbbdb9: vmovq %xmm0,%r14
0x00007ff2e4dbbdbe: vmovq %rbx,%xmm2
0x00007ff2e4dbbdc3: vmovq %rbp,%xmm1
0x00007ff2e4dbbdc8: vmovq %r13,%xmm3
0x00007ff2e4dbbdcd: vandpd 0xffe683cb(%rip),%xmm2,%xmm0
;*invokestatic sin
; - Whosebug.TrigBench::testMethod@4 (line 38)
; {external_word}
0x00007ff2e4dbbdd5: vucomisd %xmm0,%xmm3
0x00007ff2e4dbbdd9: jnb 0x7ff2e4dbbe56
0x00007ff2e4dbbddb: vmovq %xmm3,%r13
0x00007ff2e4dbbde0: vmovq %xmm1,%rbp
0x00007ff2e4dbbde5: vmovq %xmm2,%rbx
0x00007ff2e4dbbdea: vmovapd %xmm2,%xmm0
0x00007ff2e4dbbdee: movabs [=11=]x7ff2f9abaeec,%r10
0x00007ff2e4dbbdf8: callq %r10
0x00007ff2e4dbbdfb: vmovsd %xmm0,(%rsp)
0x00007ff2e4dbbe00: vmovq %rbx,%xmm2
0x00007ff2e4dbbe05: vmovq %rbp,%xmm1
0x00007ff2e4dbbe0a: vmovq %r13,%xmm3 ;*invokestatic sin
; - Whosebug.TrigBench::testMethod@12 (line 39)
0x00007ff2e4dbbe0f: vandpd 0xffe68389(%rip),%xmm2,%xmm0
;*invokestatic sin
; - Whosebug.TrigBench::testMethod@4 (line 38)
; {external_word}
0x00007ff2e4dbbe17: vucomisd %xmm0,%xmm3
0x00007ff2e4dbbe1b: jnb 0x7ff2e4dbbe32
0x00007ff2e4dbbe1d: vmovapd %xmm2,%xmm0
0x00007ff2e4dbbe21: movabs [=11=]x7ff2f9abaeec,%r10
0x00007ff2e4dbbe2b: callq %r10
0x00007ff2e4dbbe2e: vmovapd %xmm0,%xmm1 ;*invokestatic sin
; - Whosebug.TrigBench::testMethod@20 (line 40)
0x00007ff2e4dbbe32: vmovq %r14,%xmm0
0x00007ff2e4dbbe37: vaddsd (%rsp),%xmm0,%xmm0
0x00007ff2e4dbbe3c: vaddsd %xmm0,%xmm1,%xmm0 ;*dadd
; - Whosebug.TrigBench::testMethod@30 (line 41)
0x00007ff2e4dbbe40: add [=11=]x20,%rsp
0x00007ff2e4dbbe44: pop %rbp
0x00007ff2e4dbbe45: test %eax,0x15f461b5(%rip) ; {poll_return}
0x00007ff2e4dbbe4b: retq
0x00007ff2e4dbbe4c: vmovq %xmm1,%r14
0x00007ff2e4dbbe51: jmpq 0x7ff2e4dbbdcd
0x00007ff2e4dbbe56: vmovsd %xmm1,(%rsp)
0x00007ff2e4dbbe5b: jmp 0x7ff2e4dbbe0f
就在前面,我们看到生成的代码将字段 i
加载到 x87 FP 堆栈 1 并使用 fsin
指令计算Math.sin(i)
.
接下来的部分也很有趣:
0x00007ff2e4dbbd7f: vmovsd 0xffffff99(%rip),%xmm3 ; {section_word}
0x00007ff2e4dbbd87: vandpd 0xffe68411(%rip),%xmm2,%xmm0
; {external_word}
0x00007ff2e4dbbd8f: vucomisd %xmm0,%xmm3
0x00007ff2e4dbbd93: jnb 0x7ff2e4dbbe4c
第一条指令加载常量0x3fe921fb54442d18
,即0.785398...
,也称为pi / 4
。第二个是 vpand
将值 i
与其他一些常量相结合。然后我们将 pi / 4
与 vpand
的结果进行比较,如果后者小于或等于前者,则跳转到某处。
嗯?如果你跟着跳转,就会有一系列(冗余的)vpandpd
和 vucomisd
指令针对相同的值(并且对 vpand
使用相同的常量),这很快就会导致这个序列:
0x00007ff2e4dbbe32: vmovq %r14,%xmm0
0x00007ff2e4dbbe37: vaddsd (%rsp),%xmm0,%xmm0
0x00007ff2e4dbbe3c: vaddsd %xmm0,%xmm1,%xmm0 ;*dadd
...
0x00007ff2e4dbbe4b: retq
这只是使 fsin
调用返回的值(在各种跳转过程中已隐藏在 r14
和 [rsp]
中)和 returns 的三倍。
所以我们在这里看到,在 "jumps are taken" 的情况下,对 Math.sin(i)
的两次冗余调用已被消除,尽管消除仍然明确地将所有值加在一起,就好像它们是唯一的一样做了一堆冗余 and
和比较指令。
如果我们不跳转,我们会得到与您在反汇编中显示的相同的 callq %r10
行为。
这是怎么回事?
如果我们深入研究 hotspot JVM source 中的 inline_trig
调用 library_call.cpp
,我们将会找到启示。在这个方法的开始附近,我们看到了这个(为简洁起见省略了一些代码):
// Rounding required? Check for argument reduction!
if (Matcher::strict_fp_requires_explicit_rounding) {
// (snip)
// Pseudocode for sin:
// if (x <= Math.PI / 4.0) {
// if (x >= -Math.PI / 4.0) return fsin(x);
// if (x >= -Math.PI / 2.0) return -fcos(x + Math.PI / 2.0);
// } else {
// if (x <= Math.PI / 2.0) return fcos(x - Math.PI / 2.0);
// }
// return StrictMath.sin(x);
// (snip)
// Actually, sticking in an 80-bit Intel value into C2 will be tough; it
// requires a special machine instruction to load it. Instead we'll try
// the 'easy' case. If we really need the extra range +/- PI/2 we'll
// probably do the math inside the SIN encoding.
// Make the merge point
RegionNode* r = new RegionNode(3);
Node* phi = new PhiNode(r, Type::DOUBLE);
// Flatten arg so we need only 1 test
Node *abs = _gvn.transform(new AbsDNode(arg));
// Node for PI/4 constant
Node *pi4 = makecon(TypeD::make(pi_4));
// Check PI/4 : abs(arg)
Node *cmp = _gvn.transform(new CmpDNode(pi4,abs));
// Check: If PI/4 < abs(arg) then go slow
Node *bol = _gvn.transform(new BoolNode( cmp, BoolTest::lt ));
// Branch either way
IfNode *iff = create_and_xform_if(control(),bol, PROB_STATIC_FREQUENT, COUNT_UNKNOWN);
set_control(opt_iff(r,iff));
// Set fast path result
phi->init_req(2, n);
// Slow path - non-blocking leaf call
Node* call = NULL;
switch (id) {
case vmIntrinsics::_dsin:
call = make_runtime_call(RC_LEAF, OptoRuntime::Math_D_D_Type(),
CAST_FROM_FN_PTR(address, SharedRuntime::dsin),
"Sin", NULL, arg, top());
break;
break;
}
基本上,触发方法有一个快速路径和慢速路径——如果sin
的参数是大于 Math.PI / 4
我们使用慢速路径。检查涉及一个 Math.abs
调用,这就是神秘的 vandpd 0xffe68411(%rip),%xmm2,%xmm0
正在做的事情:它正在清除最高位,这是对 SSE 或 AVX 中的浮点值执行 abs
的快速方法寄存器。
现在剩下的代码也有意义了:我们看到的大部分代码是优化后的三个快速路径:两个冗余的 fsin
调用已被消除,但周围的检查没有。这可能只是优化器的局限性:要么优化器不够强大,无法消除所有内容,要么这些内部方法的扩展发生在将它们组合在一起的优化阶段之后2.
在慢速路径上,我们执行 make_runtime_call
调用,显示为 callq %r10
。这是一个所谓的存根方法调用,它在内部将实现 sin
,包括评论中提到的 "argument reduction" 问题。在我的系统上,慢速路径不一定比快速路径慢很多:如果你在 i
的初始化中将 -
更改为 +
:
private double i = Math.PI / 4 - 0.01;
您调用慢速路径,对于 单个 Math.sin(i)
调用需要约 50 ns,而快速路径需要 40 ns3。三个冗余 Math.sin(i)
调用的优化会出现问题。正如我们从上面的源代码中看到的那样,callq %r10
出现了三次(并且通过跟踪执行路径,我们看到它们都在第一次跳转失败后被执行)。这意味着三个调用的运行时间约为 150 ns,几乎是快速路径情况的 4 倍。
显然,在这种情况下,JDK 无法组合 runtime_call
节点,即使它们用于相同的参数。内部表示中的 runtime_call
节点很可能相对不透明,不受 CSE 和其他有帮助的优化的影响。这些调用主要用于内在扩展和一些 JVM 内部方法,并不会真正成为此类优化的关键目标,因此这种方法似乎是合理的。
最近 Java 9
所有这些都在 Java 9 with this change 中发生了变化。
直接内联 fsin
的 "fast path" 已被删除。我在 "fast path" 周围使用引号是有意的:当然有理由相信 SSE 或 AVX 感知软件 sin
方法可能比尚未得到的 x87 fsin
更快十多年来有很多爱。实际上,此更改正在替换 fsin
调用 "using Intel LIBM implementation" (here is the algorithm in its full glory for those that are interested).
很好,所以现在可能更快(可能 - OP 没有提供数字,即使在请求之后,所以我们不知道) - 但副作用是在没有内联的情况下,我们总是明确地调用出现在源代码中的每个 Math.sin
和 Math.cos
:没有 CSE 发生。
您可能会将此作为热点错误归档,尤其是因为它可以定位为回归 - 尽管我怀疑将已知相同参数重复传递给触发函数的用例非常少。即使是合理的性能错误,经过清楚的解释和记录,也常常会被搁置多年(当然,除非您与 Oracle 签订了付费支持合同——那么这种搁浅的情况会少一些)。
1 实际上是一种相当愚蠢、迂回的方式:它从内存中的 [rsi + 0x10]
开始,然后从那里加载到 xmm2
,然后reg-reg 是否移入 xmm1
并将其 返回 存储到堆栈顶部的内存中 (vmovsd %xmm1,(%rsp)
),然后最终将其加载到 x87 FP 堆栈 fldl (%rsp)
。当然,它可以直接从 [rsp + 0x10]
的原始位置使用单个 fld
加载它!这可能会使总延迟增加 5 个周期或更多。
2 应该注意的是 fsin
指令在这里控制运行时,所以额外的东西并没有真正给运行时添加任何东西:如果你将方法减少到单个 return Math.sin(i);
行运行时在 40ns 时大致相同。
3 至少对于接近 Math.PI / 4
的参数。在该范围之外,时间会有所不同 - 对于接近 pi / 2
的值非常快(大约 40 ns - 与 "fast path" 一样快)并且对于非常大的值通常约为 65 ns,这可能会减少通过 division/mod.
使用 jmh 对以下 Java 代码进行基准测试:
interface MyInterface {
public int test(int i);
}
class A implements MyInterface {
public int test(int i) {
return (int)Math.sin(Math.cos(i));
}
}
@State(Scope.Thread)
public class MyBenchmark {
public MyInterface inter;
@Setup(Level.Trial)
public void init() {
inter = new A();
}
@Benchmark
public void testMethod(Blackhole sink) {
int[] res = new int[2];
res[0] = inter.test(1);
res[1] = inter.test(1);
sink.consume(res);
}
}
使用 mvn package && java -XX:-UseCompressedOops -XX:CompileCommand='print, *.testMethod' -jar target/benchmarks.jar -wi 10 -i 1 -f 1
,我能够得到程序集,如果我们关注 C2 中的那个(如下所示),我们可以看到 cos
和 sin
被调用了两次。
ImmutableOopMap{}pc offsets: 796 812 828 Compiled method (c2) 402 563 4 org.sample.MyBenchmark::testMethod (42 bytes)
total in heap [0x00007efd3d74fb90,0x00007efd3d7503a0] = 2064
relocation [0x00007efd3d74fcd0,0x00007efd3d74fd08] = 56
constants [0x00007efd3d74fd20,0x00007efd3d74fd40] = 32
main code [0x00007efd3d74fd40,0x00007efd3d750040] = 768
stub code [0x00007efd3d750040,0x00007efd3d750068] = 40
oops [0x00007efd3d750068,0x00007efd3d750070] = 8
metadata [0x00007efd3d750070,0x00007efd3d750080] = 16
scopes data [0x00007efd3d750080,0x00007efd3d750108] = 136
scopes pcs [0x00007efd3d750108,0x00007efd3d750358] = 592
dependencies [0x00007efd3d750358,0x00007efd3d750360] = 8
handler table [0x00007efd3d750360,0x00007efd3d750390] = 48
nul chk table [0x00007efd3d750390,0x00007efd3d7503a0] = 16
----------------------------------------------------------------------
org/sample/MyBenchmark.testMethod(Lorg/openjdk/jmh/infra/Blackhole;)V [0x00007efd3d74fd40, 0x00007efd3d750068] 808 bytes
[Constants]
0x00007efd3d74fd20 (offset: 0): 0x00000000 0x3ff0000000000000
0x00007efd3d74fd24 (offset: 4): 0x3ff00000
0x00007efd3d74fd28 (offset: 8): 0xf4f4f4f4 0xf4f4f4f4f4f4f4f4
0x00007efd3d74fd2c (offset: 12): 0xf4f4f4f4
0x00007efd3d74fd30 (offset: 16): 0xf4f4f4f4 0xf4f4f4f4f4f4f4f4
0x00007efd3d74fd34 (offset: 20): 0xf4f4f4f4
0x00007efd3d74fd38 (offset: 24): 0xf4f4f4f4 0xf4f4f4f4f4f4f4f4
0x00007efd3d74fd3c (offset: 28): 0xf4f4f4f4
Argument 0 is unknown.RIP: 0x7efd3d74fd40 Code size: 0x00000328
[Entry Point]
# {method} {0x00007efd35857f08} 'testMethod' '(Lorg/openjdk/jmh/infra/Blackhole;)V' in 'org/sample/MyBenchmark'
# this: rsi:rsi = 'org/sample/MyBenchmark'
# parm0: rdx:rdx = 'org/openjdk/jmh/infra/Blackhole'
# [sp+0x30] (sp of caller)
0x00007efd3d74fd40: cmp 0x8(%rsi),%rax ; {no_reloc}
0x00007efd3d74fd44: jne 0x7efd35c99c60 ; {runtime_call ic_miss_stub}
0x00007efd3d74fd4a: nop
0x00007efd3d74fd4c: nopl 0x0(%rax)
[Verified Entry Point]
0x00007efd3d74fd50: mov %eax,0xfffffffffffec000(%rsp)
0x00007efd3d74fd57: push %rbp
0x00007efd3d74fd58: sub [=11=]x20,%rsp ;*synchronization entry
; - org.sample.MyBenchmark::testMethod@-1 (line 64)
0x00007efd3d74fd5c: mov %rdx,(%rsp)
0x00007efd3d74fd60: mov %rsi,%rbp
0x00007efd3d74fd63: mov 0x60(%r15),%rbx
0x00007efd3d74fd67: mov %rbx,%r10
0x00007efd3d74fd6a: add [=11=]x1a8,%r10
0x00007efd3d74fd71: cmp 0x70(%r15),%r10
0x00007efd3d74fd75: jnb 0x7efd3d74ffcc
0x00007efd3d74fd7b: mov %r10,0x60(%r15)
0x00007efd3d74fd7f: prefetchnta 0xc0(%r10)
0x00007efd3d74fd87: movq [=11=]x1,(%rbx)
0x00007efd3d74fd8e: prefetchnta 0x100(%r10)
0x00007efd3d74fd96: mov %rbx,%rdi
0x00007efd3d74fd99: add [=11=]x18,%rdi
0x00007efd3d74fd9d: prefetchnta 0x140(%r10)
0x00007efd3d74fda5: prefetchnta 0x180(%r10)
0x00007efd3d74fdad: movabs [=11=]x7efd350d9b38,%r10 ; {metadata({type array int})}
0x00007efd3d74fdb7: mov %r10,0x8(%rbx)
0x00007efd3d74fdbb: movl [=11=]x64,0x10(%rbx)
0x00007efd3d74fdc2: mov [=11=]x32,%ecx
0x00007efd3d74fdc7: xor %rax,%rax
0x00007efd3d74fdca: shl [=11=]x3,%rcx
0x00007efd3d74fdce: rep stosb (%rdi) ;*newarray {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@4 (line 65)
0x00007efd3d74fdd1: mov 0x10(%rbp),%r10 ;*getfield inter {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@20 (line 67)
0x00007efd3d74fdd5: mov 0x8(%r10),%r10 ; implicit exception: dispatches to 0x00007efd3d74fffd
0x00007efd3d74fdd9: movabs [=11=]x7efd3587f8c8,%r11 ; {metadata('org/sample/A')}
0x00007efd3d74fde3: cmp %r11,%r10
0x00007efd3d74fde6: jne 0x7efd3d74fffd ;*synchronization entry
; - org.sample.A::test@-1 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fdec: vmovsd 0xffffff2c(%rip),%xmm0 ; {section_word}
0x00007efd3d74fdf4: vmovq %xmm0,%r13
0x00007efd3d74fdf9: movabs [=11=]x7efd35c53b33,%r10
0x00007efd3d74fe03: callq %r10 ;*invokestatic cos {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@2 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fe06: movabs [=11=]x7efd35c5349c,%r10
0x00007efd3d74fe10: callq %r10 ;*invokestatic sin {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@5 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fe13: vcvttsd2si %xmm0,%r11d
0x00007efd3d74fe17: cmp [=11=]x80000000,%r11d
0x00007efd3d74fe1e: jne 0x7efd3d74fe30
0x00007efd3d74fe20: sub [=11=]x8,%rsp
0x00007efd3d74fe24: vmovsd %xmm0,(%rsp)
0x00007efd3d74fe29: callq 0x7efd35ca745b ; {runtime_call StubRoutines (2)}
0x00007efd3d74fe2e: pop %r11
0x00007efd3d74fe30: mov %r11d,0x18(%rbx) ;*iastore {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@29 (line 67)
0x00007efd3d74fe34: mov [=11=]x1,%ebp
0x00007efd3d74fe39: jmp 0x7efd3d74fe43
0x00007efd3d74fe3b: nopl 0x0(%rax,%rax)
0x00007efd3d74fe40: mov %r11d,%ebp ;*synchronization entry
; - org.sample.A::test@-1 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fe43: vmovq %r13,%xmm0
0x00007efd3d74fe48: movabs [=11=]x7efd35c53b33,%r10
0x00007efd3d74fe52: callq %r10 ;*invokestatic cos {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@2 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fe55: movabs [=11=]x7efd35c5349c,%r10
0x00007efd3d74fe5f: callq %r10 ;*invokestatic sin {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@5 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fe62: vcvttsd2si %xmm0,%r11d
0x00007efd3d74fe66: cmp [=11=]x80000000,%r11d
0x00007efd3d74fe6d: jne 0x7efd3d74fe7f
0x00007efd3d74fe6f: sub [=11=]x8,%rsp
0x00007efd3d74fe73: vmovsd %xmm0,(%rsp)
0x00007efd3d74fe78: callq 0x7efd35ca745b ; {runtime_call StubRoutines (2)}
0x00007efd3d74fe7d: pop %r11
0x00007efd3d74fe7f: mov %r11d,0x18(%rbx,%rbp,4) ;*synchronization entry
; - org.sample.A::test@-1 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fe84: vmovq %r13,%xmm0
0x00007efd3d74fe89: movabs [=11=]x7efd35c53b33,%r10
0x00007efd3d74fe93: callq %r10 ;*invokestatic cos {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@2 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fe96: movabs [=11=]x7efd35c5349c,%r10
0x00007efd3d74fea0: callq %r10 ;*invokestatic sin {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@5 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fea3: vcvttsd2si %xmm0,%r11d
0x00007efd3d74fea7: cmp [=11=]x80000000,%r11d
0x00007efd3d74feae: jne 0x7efd3d74fec0
0x00007efd3d74feb0: sub [=11=]x8,%rsp
0x00007efd3d74feb4: vmovsd %xmm0,(%rsp)
0x00007efd3d74feb9: callq 0x7efd35ca745b ; {runtime_call StubRoutines (2)}
0x00007efd3d74febe: pop %r11
0x00007efd3d74fec0: mov %r11d,0x1c(%rbx,%rbp,4) ;*synchronization entry
; - org.sample.A::test@-1 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fec5: vmovq %r13,%xmm0
0x00007efd3d74feca: movabs [=11=]x7efd35c53b33,%r10
0x00007efd3d74fed4: callq %r10 ;*invokestatic cos {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@2 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fed7: movabs [=11=]x7efd35c5349c,%r10
0x00007efd3d74fee1: callq %r10 ;*invokestatic sin {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@5 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74fee4: vcvttsd2si %xmm0,%r11d
0x00007efd3d74fee8: cmp [=11=]x80000000,%r11d
0x00007efd3d74feef: jne 0x7efd3d74ff01
0x00007efd3d74fef1: sub [=11=]x8,%rsp
0x00007efd3d74fef5: vmovsd %xmm0,(%rsp)
0x00007efd3d74fefa: callq 0x7efd35ca745b ; {runtime_call StubRoutines (2)}
0x00007efd3d74feff: pop %r11
0x00007efd3d74ff01: mov %r11d,0x20(%rbx,%rbp,4) ;*synchronization entry
; - org.sample.A::test@-1 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74ff06: vmovq %r13,%xmm0
0x00007efd3d74ff0b: movabs [=11=]x7efd35c53b33,%r10
0x00007efd3d74ff15: callq %r10 ;*invokestatic cos {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@2 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74ff18: movabs [=11=]x7efd35c5349c,%r10
0x00007efd3d74ff22: callq %r10 ;*invokestatic sin {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@5 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74ff25: vcvttsd2si %xmm0,%r11d
0x00007efd3d74ff29: cmp [=11=]x80000000,%r11d
0x00007efd3d74ff30: jne 0x7efd3d74ff42
0x00007efd3d74ff32: sub [=11=]x8,%rsp
0x00007efd3d74ff36: vmovsd %xmm0,(%rsp)
0x00007efd3d74ff3b: callq 0x7efd35ca745b ; {runtime_call StubRoutines (2)}
0x00007efd3d74ff40: pop %r11
0x00007efd3d74ff42: mov %r11d,0x24(%rbx,%rbp,4) ;*iastore {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@29 (line 67)
0x00007efd3d74ff47: mov %ebp,%r11d
0x00007efd3d74ff4a: add [=11=]x4,%r11d ;*iinc {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@30 (line 66)
0x00007efd3d74ff4e: cmp [=11=]x61,%r11d
0x00007efd3d74ff52: jl 0x7efd3d74fe40 ;*if_icmpge {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@13 (line 66)
0x00007efd3d74ff58: cmp [=11=]x64,%r11d
0x00007efd3d74ff5c: jnl 0x7efd3d74ffac
0x00007efd3d74ff5e: add [=11=]x4,%ebp ;*iinc {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@30 (line 66)
0x00007efd3d74ff61: nop ;*synchronization entry
; - org.sample.A::test@-1 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74ff64: vmovq %r13,%xmm0
0x00007efd3d74ff69: movabs [=11=]x7efd35c53b33,%r10
0x00007efd3d74ff73: callq %r10 ;*invokestatic cos {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@2 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74ff76: movabs [=11=]x7efd35c5349c,%r10
0x00007efd3d74ff80: callq %r10 ;*invokestatic sin {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.A::test@5 (line 49)
; - org.sample.MyBenchmark::testMethod@24 (line 67)
0x00007efd3d74ff83: vcvttsd2si %xmm0,%r10d
0x00007efd3d74ff87: cmp [=11=]x80000000,%r10d
0x00007efd3d74ff8e: jne 0x7efd3d74ffa0
0x00007efd3d74ff90: sub [=11=]x8,%rsp
0x00007efd3d74ff94: vmovsd %xmm0,(%rsp)
0x00007efd3d74ff99: callq 0x7efd35ca745b ; {runtime_call StubRoutines (2)}
0x00007efd3d74ff9e: pop %r10
0x00007efd3d74ffa0: mov %r10d,0x18(%rbx,%rbp,4) ;*iastore {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@29 (line 67)
0x00007efd3d74ffa5: incl %ebp ;*iinc {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@30 (line 66)
0x00007efd3d74ffa7: cmp [=11=]x64,%ebp
0x00007efd3d74ffaa: jl 0x7efd3d74ff64
0x00007efd3d74ffac: mov (%rsp),%rsi
0x00007efd3d74ffb0: test %rsi,%rsi
0x00007efd3d74ffb3: je 0x7efd3d74ffe8 ;*if_icmpge {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@13 (line 66)
0x00007efd3d74ffb5: mov %rbx,%rdx
0x00007efd3d74ffb8: nop
0x00007efd3d74ffbb: callq 0x7efd362c50e0 ; ImmutableOopMap{}
;*invokevirtual consume {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@38 (line 69)
; {optimized virtual_call}
0x00007efd3d74ffc0: add [=11=]x20,%rsp
0x00007efd3d74ffc4: pop %rbp
0x00007efd3d74ffc5: test %eax,0x18f98035(%rip) ; {poll_return}
0x00007efd3d74ffcb: retq
0x00007efd3d74ffcc: mov [=11=]x64,%edx
0x00007efd3d74ffd1: movabs [=11=]x7efd350d9b38,%rsi ; {metadata({type array int})}
0x00007efd3d74ffdb: callq 0x7efd35d5fd60 ; ImmutableOopMap{rbp=Oop [0]=Oop }
;*newarray {reexecute=0 rethrow=0 return_oop=1}
; - org.sample.MyBenchmark::testMethod@4 (line 65)
; {runtime_call _new_array_Java}
0x00007efd3d74ffe0: mov %rax,%rbx
0x00007efd3d74ffe3: jmpq 0x7efd3d74fdd1
0x00007efd3d74ffe8: mov [=11=]xfffffff6,%esi
0x00007efd3d74ffed: mov %rbx,%rbp
0x00007efd3d74fff0: nop
0x00007efd3d74fff3: callq 0x7efd35c9b560 ; ImmutableOopMap{rbp=Oop }
;*invokevirtual consume {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@38 (line 69)
; {runtime_call UncommonTrapBlob}
0x00007efd3d74fff8: callq 0x7efd55167aa0 ; {runtime_call}
0x00007efd3d74fffd: mov [=11=]xffffff86,%esi
0x00007efd3d750002: mov %rbx,0x8(%rsp)
0x00007efd3d750007: callq 0x7efd35c9b560 ; ImmutableOopMap{rbp=Oop [0]=Oop [8]=Oop }
;*aload_3 {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@16 (line 67)
; {runtime_call UncommonTrapBlob}
0x00007efd3d75000c: callq 0x7efd55167aa0 ;*newarray {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@4 (line 65)
; {runtime_call}
0x00007efd3d750011: mov %rax,%rsi
0x00007efd3d750014: jmp 0x7efd3d750019
0x00007efd3d750016: mov %rax,%rsi ;*invokevirtual consume {reexecute=0 rethrow=0 return_oop=0}
; - org.sample.MyBenchmark::testMethod@38 (line 69)
0x00007efd3d750019: add [=11=]x20,%rsp
0x00007efd3d75001d: pop %rbp
0x00007efd3d75001e: jmpq 0x7efd35d64160 ; {runtime_call _rethrow_Java}
0x00007efd3d750023: hlt
0x00007efd3d750024: hlt
0x00007efd3d750025: hlt
0x00007efd3d750026: hlt
0x00007efd3d750027: hlt
0x00007efd3d750028: hlt
0x00007efd3d750029: hlt
0x00007efd3d75002a: hlt
0x00007efd3d75002b: hlt
0x00007efd3d75002c: hlt
0x00007efd3d75002d: hlt
0x00007efd3d75002e: hlt
0x00007efd3d75002f: hlt
0x00007efd3d750030: hlt
0x00007efd3d750031: hlt
0x00007efd3d750032: hlt
0x00007efd3d750033: hlt
0x00007efd3d750034: hlt
0x00007efd3d750035: hlt
0x00007efd3d750036: hlt
0x00007efd3d750037: hlt
0x00007efd3d750038: hlt
0x00007efd3d750039: hlt
0x00007efd3d75003a: hlt
0x00007efd3d75003b: hlt
0x00007efd3d75003c: hlt
0x00007efd3d75003d: hlt
0x00007efd3d75003e: hlt
0x00007efd3d75003f: hlt
我期待 inter.test
的结果被缓存或其他东西,以便 inter.test
(sin 和 cos)只被调用一次。我可以使用任何选项来使 JVM (JIT) 这样做吗?或者是什么阻止了 JVM (JIT) 看到该方法是纯方法?
环境:
$ java -version
openjdk version "9-internal"
OpenJDK Runtime Environment (build 9-internal+0-2016-04-14-195246.buildd.src)
OpenJDK 64-Bit Server VM (build 9-internal+0-2016-04-14-195246.buildd.src, mixed mode)
# jmh version
<jmh.version>1.19</jmh.version>
据我所知,
也就是说,如果对纯方法的冗余调用都在调用点内联,则通过 CSE 和 GVN 等常规优化间接在内联代码中检测到冗余,这样额外的成本来电通常会消失。但是,如果方法未内联,我认为 JVM 不会将它们标记为 "pure",因此无法消除它们(与 many native compilers which can 不同)。
仍然考虑到内联 可以 删除冗余调用,问题仍然存在:为什么冗余 Math.sin
和 Math.cos
调用没有内联并最终优化走了?
事实证明,Math.sin
和 Math.cos
与其他几个 Math
和 JDK 中的其他方法一样被特殊处理为 intrinsic functions。下面您将详细了解 Java 8 和 Java 9 的一些版本中发生的情况。您展示的反汇编来自 Java 9 的更高版本,它以不同的方式处理这个问题,覆盖在最后。
在 JVM 中处理触发方法的方式是...复杂。原则上,Math.sin
和 Math.cos
在 x86 上使用本机 FP 指令内联为内部方法,但有一些注意事项。
您的基准测试中有很多无关因素使其更难分析,例如数组分配、对 [=23= 的调用]、Math.sin
和 [=17 的使用=]、传递常量(这可能会导致某些触发指令被完全优化)、接口的使用 A
和该接口的实现等
取而代之的是,让我们去掉那个垃圾并将其简化为一个更简单的版本,它只使用相同的参数调用 Math.sin(x)
三次,并且 returns 总和:
private double i = Math.PI / 4 - 0.01;
@Benchmark
public double testMethod() {
double res0 = Math.sin(i);
double res1 = Math.sin(i);
double res2 = Math.sin(i);
return res0 + res1 + res2;
}
运行 这与 JHM args -bm avgt -tu ns -wi 5 -f 1 -i 5
我得到大约 40 ns/op,这是在现代 x86 上的单个 fsin
调用范围的下限硬件。让我们来看看汇编:
[Constants]
0x00007ff2e4dbbd20 (offset: 0): 0x54442d18 0x3fe921fb54442d18
0x00007ff2e4dbbd24 (offset: 4): 0x3fe921fb
0x00007ff2e4dbbd28 (offset: 8): 0xf4f4f4f4 0xf4f4f4f4f4f4f4f4
0x00007ff2e4dbbd2c (offset: 12): 0xf4f4f4f4
0x00007ff2e4dbbd30 (offset: 16): 0xf4f4f4f4 0xf4f4f4f4f4f4f4f4
0x00007ff2e4dbbd34 (offset: 20): 0xf4f4f4f4
0x00007ff2e4dbbd38 (offset: 24): 0xf4f4f4f4 0xf4f4f4f4f4f4f4f4
0x00007ff2e4dbbd3c (offset: 28): 0xf4f4f4f4
(snip)
[Verified Entry Point]
0x00007ff2e4dbbd50: sub [=11=]x28,%rsp
0x00007ff2e4dbbd57: mov %rbp,0x20(%rsp) ;*synchronization entry
; - Whosebug.TrigBench::testMethod@-1 (line 38)
0x00007ff2e4dbbd5c: vmovsd 0x10(%rsi),%xmm2 ;*getfield i
; - Whosebug.TrigBench::testMethod@1 (line 38)
0x00007ff2e4dbbd61: vmovapd %xmm2,%xmm1
0x00007ff2e4dbbd65: sub [=11=]x8,%rsp
0x00007ff2e4dbbd69: vmovsd %xmm1,(%rsp)
0x00007ff2e4dbbd6e: fldl (%rsp)
0x00007ff2e4dbbd71: fsin
0x00007ff2e4dbbd73: fstpl (%rsp)
0x00007ff2e4dbbd76: vmovsd (%rsp),%xmm1
0x00007ff2e4dbbd7b: add [=11=]x8,%rsp ;*invokestatic sin
; - Whosebug.TrigBench::testMethod@20 (line 40)
0x00007ff2e4dbbd7f: vmovsd 0xffffff99(%rip),%xmm3 ; {section_word}
0x00007ff2e4dbbd87: vandpd 0xffe68411(%rip),%xmm2,%xmm0
; {external_word}
0x00007ff2e4dbbd8f: vucomisd %xmm0,%xmm3
0x00007ff2e4dbbd93: jnb 0x7ff2e4dbbe4c
0x00007ff2e4dbbd99: vmovq %xmm3,%r13
0x00007ff2e4dbbd9e: vmovq %xmm1,%rbp
0x00007ff2e4dbbda3: vmovq %xmm2,%rbx
0x00007ff2e4dbbda8: vmovapd %xmm2,%xmm0
0x00007ff2e4dbbdac: movabs [=11=]x7ff2f9abaeec,%r10
0x00007ff2e4dbbdb6: callq %r10
0x00007ff2e4dbbdb9: vmovq %xmm0,%r14
0x00007ff2e4dbbdbe: vmovq %rbx,%xmm2
0x00007ff2e4dbbdc3: vmovq %rbp,%xmm1
0x00007ff2e4dbbdc8: vmovq %r13,%xmm3
0x00007ff2e4dbbdcd: vandpd 0xffe683cb(%rip),%xmm2,%xmm0
;*invokestatic sin
; - Whosebug.TrigBench::testMethod@4 (line 38)
; {external_word}
0x00007ff2e4dbbdd5: vucomisd %xmm0,%xmm3
0x00007ff2e4dbbdd9: jnb 0x7ff2e4dbbe56
0x00007ff2e4dbbddb: vmovq %xmm3,%r13
0x00007ff2e4dbbde0: vmovq %xmm1,%rbp
0x00007ff2e4dbbde5: vmovq %xmm2,%rbx
0x00007ff2e4dbbdea: vmovapd %xmm2,%xmm0
0x00007ff2e4dbbdee: movabs [=11=]x7ff2f9abaeec,%r10
0x00007ff2e4dbbdf8: callq %r10
0x00007ff2e4dbbdfb: vmovsd %xmm0,(%rsp)
0x00007ff2e4dbbe00: vmovq %rbx,%xmm2
0x00007ff2e4dbbe05: vmovq %rbp,%xmm1
0x00007ff2e4dbbe0a: vmovq %r13,%xmm3 ;*invokestatic sin
; - Whosebug.TrigBench::testMethod@12 (line 39)
0x00007ff2e4dbbe0f: vandpd 0xffe68389(%rip),%xmm2,%xmm0
;*invokestatic sin
; - Whosebug.TrigBench::testMethod@4 (line 38)
; {external_word}
0x00007ff2e4dbbe17: vucomisd %xmm0,%xmm3
0x00007ff2e4dbbe1b: jnb 0x7ff2e4dbbe32
0x00007ff2e4dbbe1d: vmovapd %xmm2,%xmm0
0x00007ff2e4dbbe21: movabs [=11=]x7ff2f9abaeec,%r10
0x00007ff2e4dbbe2b: callq %r10
0x00007ff2e4dbbe2e: vmovapd %xmm0,%xmm1 ;*invokestatic sin
; - Whosebug.TrigBench::testMethod@20 (line 40)
0x00007ff2e4dbbe32: vmovq %r14,%xmm0
0x00007ff2e4dbbe37: vaddsd (%rsp),%xmm0,%xmm0
0x00007ff2e4dbbe3c: vaddsd %xmm0,%xmm1,%xmm0 ;*dadd
; - Whosebug.TrigBench::testMethod@30 (line 41)
0x00007ff2e4dbbe40: add [=11=]x20,%rsp
0x00007ff2e4dbbe44: pop %rbp
0x00007ff2e4dbbe45: test %eax,0x15f461b5(%rip) ; {poll_return}
0x00007ff2e4dbbe4b: retq
0x00007ff2e4dbbe4c: vmovq %xmm1,%r14
0x00007ff2e4dbbe51: jmpq 0x7ff2e4dbbdcd
0x00007ff2e4dbbe56: vmovsd %xmm1,(%rsp)
0x00007ff2e4dbbe5b: jmp 0x7ff2e4dbbe0f
就在前面,我们看到生成的代码将字段 i
加载到 x87 FP 堆栈 1 并使用 fsin
指令计算Math.sin(i)
.
接下来的部分也很有趣:
0x00007ff2e4dbbd7f: vmovsd 0xffffff99(%rip),%xmm3 ; {section_word}
0x00007ff2e4dbbd87: vandpd 0xffe68411(%rip),%xmm2,%xmm0
; {external_word}
0x00007ff2e4dbbd8f: vucomisd %xmm0,%xmm3
0x00007ff2e4dbbd93: jnb 0x7ff2e4dbbe4c
第一条指令加载常量0x3fe921fb54442d18
,即0.785398...
,也称为pi / 4
。第二个是 vpand
将值 i
与其他一些常量相结合。然后我们将 pi / 4
与 vpand
的结果进行比较,如果后者小于或等于前者,则跳转到某处。
嗯?如果你跟着跳转,就会有一系列(冗余的)vpandpd
和 vucomisd
指令针对相同的值(并且对 vpand
使用相同的常量),这很快就会导致这个序列:
0x00007ff2e4dbbe32: vmovq %r14,%xmm0
0x00007ff2e4dbbe37: vaddsd (%rsp),%xmm0,%xmm0
0x00007ff2e4dbbe3c: vaddsd %xmm0,%xmm1,%xmm0 ;*dadd
...
0x00007ff2e4dbbe4b: retq
这只是使 fsin
调用返回的值(在各种跳转过程中已隐藏在 r14
和 [rsp]
中)和 returns 的三倍。
所以我们在这里看到,在 "jumps are taken" 的情况下,对 Math.sin(i)
的两次冗余调用已被消除,尽管消除仍然明确地将所有值加在一起,就好像它们是唯一的一样做了一堆冗余 and
和比较指令。
如果我们不跳转,我们会得到与您在反汇编中显示的相同的 callq %r10
行为。
这是怎么回事?
如果我们深入研究 hotspot JVM source 中的 inline_trig
调用 library_call.cpp
,我们将会找到启示。在这个方法的开始附近,我们看到了这个(为简洁起见省略了一些代码):
// Rounding required? Check for argument reduction!
if (Matcher::strict_fp_requires_explicit_rounding) {
// (snip)
// Pseudocode for sin:
// if (x <= Math.PI / 4.0) {
// if (x >= -Math.PI / 4.0) return fsin(x);
// if (x >= -Math.PI / 2.0) return -fcos(x + Math.PI / 2.0);
// } else {
// if (x <= Math.PI / 2.0) return fcos(x - Math.PI / 2.0);
// }
// return StrictMath.sin(x);
// (snip)
// Actually, sticking in an 80-bit Intel value into C2 will be tough; it
// requires a special machine instruction to load it. Instead we'll try
// the 'easy' case. If we really need the extra range +/- PI/2 we'll
// probably do the math inside the SIN encoding.
// Make the merge point
RegionNode* r = new RegionNode(3);
Node* phi = new PhiNode(r, Type::DOUBLE);
// Flatten arg so we need only 1 test
Node *abs = _gvn.transform(new AbsDNode(arg));
// Node for PI/4 constant
Node *pi4 = makecon(TypeD::make(pi_4));
// Check PI/4 : abs(arg)
Node *cmp = _gvn.transform(new CmpDNode(pi4,abs));
// Check: If PI/4 < abs(arg) then go slow
Node *bol = _gvn.transform(new BoolNode( cmp, BoolTest::lt ));
// Branch either way
IfNode *iff = create_and_xform_if(control(),bol, PROB_STATIC_FREQUENT, COUNT_UNKNOWN);
set_control(opt_iff(r,iff));
// Set fast path result
phi->init_req(2, n);
// Slow path - non-blocking leaf call
Node* call = NULL;
switch (id) {
case vmIntrinsics::_dsin:
call = make_runtime_call(RC_LEAF, OptoRuntime::Math_D_D_Type(),
CAST_FROM_FN_PTR(address, SharedRuntime::dsin),
"Sin", NULL, arg, top());
break;
break;
}
基本上,触发方法有一个快速路径和慢速路径——如果sin
的参数是大于 Math.PI / 4
我们使用慢速路径。检查涉及一个 Math.abs
调用,这就是神秘的 vandpd 0xffe68411(%rip),%xmm2,%xmm0
正在做的事情:它正在清除最高位,这是对 SSE 或 AVX 中的浮点值执行 abs
的快速方法寄存器。
现在剩下的代码也有意义了:我们看到的大部分代码是优化后的三个快速路径:两个冗余的 fsin
调用已被消除,但周围的检查没有。这可能只是优化器的局限性:要么优化器不够强大,无法消除所有内容,要么这些内部方法的扩展发生在将它们组合在一起的优化阶段之后2.
在慢速路径上,我们执行 make_runtime_call
调用,显示为 callq %r10
。这是一个所谓的存根方法调用,它在内部将实现 sin
,包括评论中提到的 "argument reduction" 问题。在我的系统上,慢速路径不一定比快速路径慢很多:如果你在 i
的初始化中将 -
更改为 +
:
private double i = Math.PI / 4 - 0.01;
您调用慢速路径,对于 单个 Math.sin(i)
调用需要约 50 ns,而快速路径需要 40 ns3。三个冗余 Math.sin(i)
调用的优化会出现问题。正如我们从上面的源代码中看到的那样,callq %r10
出现了三次(并且通过跟踪执行路径,我们看到它们都在第一次跳转失败后被执行)。这意味着三个调用的运行时间约为 150 ns,几乎是快速路径情况的 4 倍。
显然,在这种情况下,JDK 无法组合 runtime_call
节点,即使它们用于相同的参数。内部表示中的 runtime_call
节点很可能相对不透明,不受 CSE 和其他有帮助的优化的影响。这些调用主要用于内在扩展和一些 JVM 内部方法,并不会真正成为此类优化的关键目标,因此这种方法似乎是合理的。
最近 Java 9
所有这些都在 Java 9 with this change 中发生了变化。
直接内联 fsin
的 "fast path" 已被删除。我在 "fast path" 周围使用引号是有意的:当然有理由相信 SSE 或 AVX 感知软件 sin
方法可能比尚未得到的 x87 fsin
更快十多年来有很多爱。实际上,此更改正在替换 fsin
调用 "using Intel LIBM implementation" (here is the algorithm in its full glory for those that are interested).
很好,所以现在可能更快(可能 - OP 没有提供数字,即使在请求之后,所以我们不知道) - 但副作用是在没有内联的情况下,我们总是明确地调用出现在源代码中的每个 Math.sin
和 Math.cos
:没有 CSE 发生。
您可能会将此作为热点错误归档,尤其是因为它可以定位为回归 - 尽管我怀疑将已知相同参数重复传递给触发函数的用例非常少。即使是合理的性能错误,经过清楚的解释和记录,也常常会被搁置多年(当然,除非您与 Oracle 签订了付费支持合同——那么这种搁浅的情况会少一些)。
1 实际上是一种相当愚蠢、迂回的方式:它从内存中的 [rsi + 0x10]
开始,然后从那里加载到 xmm2
,然后reg-reg 是否移入 xmm1
并将其 返回 存储到堆栈顶部的内存中 (vmovsd %xmm1,(%rsp)
),然后最终将其加载到 x87 FP 堆栈 fldl (%rsp)
。当然,它可以直接从 [rsp + 0x10]
的原始位置使用单个 fld
加载它!这可能会使总延迟增加 5 个周期或更多。
2 应该注意的是 fsin
指令在这里控制运行时,所以额外的东西并没有真正给运行时添加任何东西:如果你将方法减少到单个 return Math.sin(i);
行运行时在 40ns 时大致相同。
3 至少对于接近 Math.PI / 4
的参数。在该范围之外,时间会有所不同 - 对于接近 pi / 2
的值非常快(大约 40 ns - 与 "fast path" 一样快)并且对于非常大的值通常约为 65 ns,这可能会减少通过 division/mod.