JVM针对纯方法的JIT方法重新计算

8
使用 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的汇编代码(如下所示),我们可以看到cossin都被调用了两次。

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     $0x20,%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     $0x1a8,%r10
  0x00007efd3d74fd71: cmp     0x70(%r15),%r10
  0x00007efd3d74fd75: jnb     0x7efd3d74ffcc
  0x00007efd3d74fd7b: mov     %r10,0x60(%r15)
  0x00007efd3d74fd7f: prefetchnta 0xc0(%r10)
  0x00007efd3d74fd87: movq    $0x1,(%rbx)
  0x00007efd3d74fd8e: prefetchnta 0x100(%r10)
  0x00007efd3d74fd96: mov     %rbx,%rdi
  0x00007efd3d74fd99: add     $0x18,%rdi
  0x00007efd3d74fd9d: prefetchnta 0x140(%r10)
  0x00007efd3d74fda5: prefetchnta 0x180(%r10)
  0x00007efd3d74fdad: movabs  $0x7efd350d9b38,%r10  ;   {metadata({type array int})}
  0x00007efd3d74fdb7: mov     %r10,0x8(%rbx)
  0x00007efd3d74fdbb: movl    $0x64,0x10(%rbx)
  0x00007efd3d74fdc2: mov     $0x32,%ecx
  0x00007efd3d74fdc7: xor     %rax,%rax
  0x00007efd3d74fdca: shl     $0x3,%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  $0x7efd3587f8c8,%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  $0x7efd35c53b33,%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  $0x7efd35c5349c,%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     $0x80000000,%r11d
  0x00007efd3d74fe1e: jne     0x7efd3d74fe30
  0x00007efd3d74fe20: sub     $0x8,%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     $0x1,%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  $0x7efd35c53b33,%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  $0x7efd35c5349c,%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     $0x80000000,%r11d
  0x00007efd3d74fe6d: jne     0x7efd3d74fe7f
  0x00007efd3d74fe6f: sub     $0x8,%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  $0x7efd35c53b33,%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  $0x7efd35c5349c,%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     $0x80000000,%r11d
  0x00007efd3d74feae: jne     0x7efd3d74fec0
  0x00007efd3d74feb0: sub     $0x8,%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  $0x7efd35c53b33,%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  $0x7efd35c5349c,%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     $0x80000000,%r11d
  0x00007efd3d74feef: jne     0x7efd3d74ff01
  0x00007efd3d74fef1: sub     $0x8,%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  $0x7efd35c53b33,%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  $0x7efd35c5349c,%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     $0x80000000,%r11d
  0x00007efd3d74ff30: jne     0x7efd3d74ff42
  0x00007efd3d74ff32: sub     $0x8,%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     $0x4,%r11d        ;*iinc {reexecute=0 rethrow=0 return_oop=0}
                                                ; - org.sample.MyBenchmark::testMethod@30 (line 66)

  0x00007efd3d74ff4e: cmp     $0x61,%r11d
  0x00007efd3d74ff52: jl      0x7efd3d74fe40    ;*if_icmpge {reexecute=0 rethrow=0 return_oop=0}
                                                ; - org.sample.MyBenchmark::testMethod@13 (line 66)

  0x00007efd3d74ff58: cmp     $0x64,%r11d
  0x00007efd3d74ff5c: jnl     0x7efd3d74ffac
  0x00007efd3d74ff5e: add     $0x4,%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  $0x7efd35c53b33,%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  $0x7efd35c5349c,%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     $0x80000000,%r10d
  0x00007efd3d74ff8e: jne     0x7efd3d74ffa0
  0x00007efd3d74ff90: sub     $0x8,%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     $0x64,%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     $0x20,%rsp
  0x00007efd3d74ffc4: pop     %rbp
  0x00007efd3d74ffc5: test    %eax,0x18f98035(%rip)  ;   {poll_return}
  0x00007efd3d74ffcb: retq
  0x00007efd3d74ffcc: mov     $0x64,%edx
  0x00007efd3d74ffd1: movabs  $0x7efd350d9b38,%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     $0xfffffff6,%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     $0xffffff86,%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     $0x20,%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>

对我来说,它似乎只是完全内联这些调用,您使用的是哪个Java和JMH版本? - Jorn Vernee
你可以使用C++进行重写,并应用const和类型检查等其他好的东西,这样编译器就能清楚地知道它在做什么。在Java中,如果要考虑性能,你需要手动操作JVM,但总体而言,我对使用Java进行性能相关编码的经验非常糟糕,你的代码只是沿用了我所见过的内容。对我来说,唯一明智的方法是接受Java是什么,一个简单的编程语言,易于学习到中级水平并取得商业成功。我找不到任何更好的东西,自从C++11以来,C++在源代码优雅方面始终胜出。 - Ped7g
你在 ns/op 方面获得了什么性能?你能展示整个方法吗?在过去的几个版本中,JDK 对 Math.sinMath.cos 使用内部函数,这归结为使用 fsinfcos 的内联快速路径以及一个较慢的路径,具体取决于参数是否需要缩小等。因此,你的摘录并不足以得出 call %r10 行实际上正在执行的结论。 - BeeOnRope
2
@AlbertNetymk - 这很有趣,因为它告诉你是否走快速或慢速路径。除非你有非常奇怪的硬件,否则它并不是那么依赖于硬件。例如,在x86硬件上,fsin的性能大约在过去十年中保持稳定。无论如何,正如我所提到的,你上面链接的代码片段可能甚至没有被执行。这是“慢速路径”,但如果你四处寻找,你可能会找到快速路径,它直接从内存位置加载最终结果(因为你的输入是常量)。 - BeeOnRope
1
你可以在此处查看这些函数的JIT代码生成逻辑(http://hg.openjdk.java.net/jdk9/jdk9/hotspot/file/a34b3268a14f/src/share/vm/opto/library_call.cpp#l1493)。特别地,在x86上,Matcher::strict_fp_requires_explicit_rounding似乎为真,然后沿着这条路线进行,这意味着小于Math.PI / 4的值将被处理得非常不同(内联、更快),而大于Math.PI / 4的值则会被处理得不同。你可以通过尝试例如Math.PI / 4 - 0.01和Math.PI / 4 + 0.01来验证这一点。对我来说差异是5倍。 - BeeOnRope
显示剩余9条评论
1个回答

11
据我所知,HotSpot无法优化纯方法的冗余调用(即针对相同参数的纯方法的调用),除非通过内联间接地进行优化。
也就是说,如果纯方法的冗余调用都在调用点处内联,那么通常的优化(如CSE和GVN)会在内联代码中间接检测到冗余,从而消除额外调用的成本。然而,如果这些方法没有被内联,JVM不会标记它们为"纯",因此无法消除它们(与许多本地编译器不同,例如)。 尽管内联可以消除冗余调用,但问题仍然存在:为什么冗余的Math.sinMath.cos调用没有被内联并最终优化掉呢?
事实证明,Math.sinMath.cos以及JDK中的其他一些Math和其他方法都被特殊处理为内置函数。下面您将详细了解Java 8和某些Java 9版本中发生的情况。您展示的反汇编是来自Java 9的较新版本,其处理方式有所不同,这将在最后介绍。
JVM中处理三角函数方法的方式是......复杂的。原则上,Math.sin和Math.cos会作为内在方法使用本机FP指令在x86上进行内联,但存在注意事项。
您的基准测试中有许多无关因素使分析变得更加困难,例如数组分配、调用Blackhole.consume、同时使用Math.sin和Math.cos、传递常量(可能会导致一些三角指令被完全优化掉)、使用接口A和该接口的实现等。
相反,让我们剥离这些无用的部分,将其简化为一个更简单的版本,只需使用相同的参数三次调用Math.sin(x),并返回总和:
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参数-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     $0x28,%rsp
  0x00007ff2e4dbbd57: mov     %rbp,0x20(%rsp)   ;*synchronization entry
                                                ; - stackoverflow.TrigBench::testMethod@-1 (line 38)

  0x00007ff2e4dbbd5c: vmovsd  0x10(%rsi),%xmm2  ;*getfield i
                                                ; - stackoverflow.TrigBench::testMethod@1 (line 38)

  0x00007ff2e4dbbd61: vmovapd %xmm2,%xmm1
  0x00007ff2e4dbbd65: sub     $0x8,%rsp
  0x00007ff2e4dbbd69: vmovsd  %xmm1,(%rsp)
  0x00007ff2e4dbbd6e: fldl    (%rsp)
  0x00007ff2e4dbbd71: fsin
  0x00007ff2e4dbbd73: fstpl   (%rsp)
  0x00007ff2e4dbbd76: vmovsd  (%rsp),%xmm1
  0x00007ff2e4dbbd7b: add     $0x8,%rsp         ;*invokestatic sin
                                                ; - stackoverflow.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  $0x7ff2f9abaeec,%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
                                                ; - stackoverflow.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  $0x7ff2f9abaeec,%r10
  0x00007ff2e4dbbdf8: callq   %r10
  0x00007ff2e4dbbdfb: vmovsd  %xmm0,(%rsp)
  0x00007ff2e4dbbe00: vmovq   %rbx,%xmm2
  0x00007ff2e4dbbe05: vmovq   %rbp,%xmm1
  0x00007ff2e4dbbe0a: vmovq   %r13,%xmm3        ;*invokestatic sin
                                                ; - stackoverflow.TrigBench::testMethod@12 (line 39)

  0x00007ff2e4dbbe0f: vandpd  0xffe68389(%rip),%xmm2,%xmm0
                                                ;*invokestatic sin
                                                ; - stackoverflow.TrigBench::testMethod@4 (line 38)
                                                ;   {external_word}
  0x00007ff2e4dbbe17: vucomisd %xmm0,%xmm3
  0x00007ff2e4dbbe1b: jnb     0x7ff2e4dbbe32
  0x00007ff2e4dbbe1d: vmovapd %xmm2,%xmm0
  0x00007ff2e4dbbe21: movabs  $0x7ff2f9abaeec,%r10
  0x00007ff2e4dbbe2b: callq   %r10
  0x00007ff2e4dbbe2e: vmovapd %xmm0,%xmm1       ;*invokestatic sin
                                                ; - stackoverflow.TrigBench::testMethod@20 (line 40)

  0x00007ff2e4dbbe32: vmovq   %r14,%xmm0
  0x00007ff2e4dbbe37: vaddsd  (%rsp),%xmm0,%xmm0
  0x00007ff2e4dbbe3c: vaddsd  %xmm0,%xmm1,%xmm0  ;*dadd
                                                ; - stackoverflow.TrigBench::testMethod@30 (line 41)

  0x00007ff2e4dbbe40: add     $0x20,%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。第二个是将值i与其他常量进行vpand操作。然后,我们将pi / 4vpand的结果进行比较,并在后者小于或等于前者时跳转到某个位置。
嗯?如果你跟着跳,会有一系列(冗余的)vpandpdvucomisd指令针对相同的值(并使用相同的常量进行vpand),这很快就会导致以下序列:
  0x00007ff2e4dbbe32: vmovq   %r14,%xmm0
  0x00007ff2e4dbbe37: vaddsd  (%rsp),%xmm0,%xmm0
  0x00007ff2e4dbbe3c: vaddsd  %xmm0,%xmm1,%xmm0  ;*dadd
  ...
  0x00007ff2e4dbbe4b: retq

这仅仅是将从fsin调用中返回的值(在各种跳转期间已经被隐藏在r14[rsp]中)的值乘以三倍,并返回。

因此,我们看到,在“采取跳转”情况下,两个冗余的Math.sin(i)调用已被消除,尽管消除仍然明确地将所有值相加,就像它们是唯一的一样,并且执行了大量冗余的and和比较指令。

如果我们不采取跳转,我们会得到与您在反汇编中显示的相同的callq %r10行为。

这里发生了什么?


如果我们深入研究hotspot JVM源代码中的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 调用,但周围的检查没有被消除。这可能只是优化器的局限性:优化器要么不够强大以至于不能消除所有东西,要么就是内置方法的扩展发生在将它们合并的优化阶段之后。
在慢速路径上,我们执行 make_runtime_call,这会显示为 callq %r10。这是所谓的存根方法调用,内部将实现 sin,包括注释中提到的“参数缩减”问题。在我的系统上,慢速路径不一定比快速路径慢得多:如果您将 i 的初始化中的 - 更改为 +
private double i = Math.PI / 4 - 0.01;

当你调用slow path时,对于单个Math.sin(i)的调用,它需要大约50纳秒,而快速路径只需要40纳秒3。这个问题出现在三个冗余的Math.sin(i)调用的优化中。正如我们从上面的源代码中看到的那样,callq %r10发生了三次(通过跟踪执行路径,我们看到它们都是在第一个jump落下后执行的)。这意味着三次调用的运行时间约为150纳秒,或者几乎是快速路径情况的4倍。

显然,在这种情况下,JDK无法将runtime_call节点组合起来,即使它们是相同的参数。最可能的是,内部表示中的runtime_call节点相对不透明,并且不受CSE和其他优化的影响。这些调用主要用于内部JVM方法的内在扩展,并不是这种优化的关键目标,因此这种方法似乎是合理的。

最近的Java 9

所有这些都在Java 9 with this change中发生了改变。

"快速路径"中直接内联的fsin已被移除。这里使用引号括起来的"快速路径"是有意义的: 有理由相信,SSE或AVX感知软件的sin方法可能比在过去十年中没有得到很多关注的x87 fsin更快。实际上,这个改变是用"Intel LIBM实现"(对于那些感兴趣的人,这里是完整算法)替换了fsin调用。
很好,现在也许更快了(也许-即使在请求后,OP也没有提供数字,所以我们不知道)-但副作用是,没有内联,我们总是明确地为源代码中出现的每个Math.sinMath.cos进行调用: 没有CSE发生。
你可能可以将此文件作为热点错误提交,特别是因为它可以被定位为回归-尽管我怀疑已知相同参数被重复传递给三角函数的用例非常少。即使是合法的性能错误,如果清楚地解释和记录,通常也会停滞多年(除非当然你与Oracle有付费支持合同-然后停滞会稍微少一些)。"

1 实际上,这种转换方式相当愚蠢、绕远路:它从内存中的[rsi + 0x10]开始,然后将其加载到xmm2,再进行寄存器-寄存器移动到xmm1,并将其储存在栈的顶部(vmovsd %xmm1,(%rsp)),最后使用fldl (%rsp)将其加载到x87 FP堆栈中。当然,它本可以通过单个fld直接从原始位置[rsp + 0x10]加载它!这可能会增加总延迟时间5周期或更多。

2 不过需要注意的是,fsin指令在这里占据着主导地位,因此额外的操作并没有真正增加运行时间:如果将该方法简化为单个return Math.sin(i);语句,则运行时间约为40纳秒。

3 至少对于接近Math.PI / 4的参数而言。超出该范围,则时序不同- 对于接近pi / 2的值非常快(约40 ns-与“快速路径”一样快),而对于非常大的值通常在65 ns左右,这可能通过除法/mod来进行约简。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接