Java 的实例真的这么快吗?

Is Java instance of really so fast?

我正在尝试测量 instance of 是否真的很快。这是非常简单的基准测试:

public Object a = 2;

@Benchmark
@Warmup(iterations = 5, timeUnit = TimeUnit.NANOSECONDS)
@Measurement(iterations = 5, timeUnit = TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public boolean test() {
    return a instanceof Double;
}

我运行这个板凳

Benchmark              Mode  Cnt  Score   Error  Units
MyBenchmark.test       avgt    5  3.105 ± 0.086  ns/op

基准测试的汇编输出太长,省略。

我也写了一个简单的java程序

private static Object i = 123;

public static boolean insOf(){
    return i instanceof Double;
}

public static void main(String[] args) throws IOException {
    for (int i = 0; i < 100000000; i++)
        if(insOf())
            System.out.print("");
}

编译后的insOf方法的汇编输出是这样的:

0x00007fd761114b60: mov     %eax,0xfffffffffffec000(%rsp)
  0x00007fd761114b67: push    %rbp
  0x00007fd761114b68: sub     [=14=]x10,%rsp        ;*synchronization entry
                                                ; - com.get.intent.App::insOf@-1 (line 24)

  0x00007fd761114b6c: movabs  [=14=]xd6f788e0,%r10  ;   {oop(a 'java/lang/Class' = 'com/get/intent/App')}
  0x00007fd761114b76: mov     0x68(%r10),%r11d  ;*getstatic i
                                                ; - com.get.intent.App::insOf@0 (line 24)

  0x00007fd761114b7a: mov     0x8(%r11),%r10d   ; implicit exception: dispatches to 0x00007fd761114b9c
  0x00007fd761114b7e: cmp     [=14=]x20002192,%r10d  ;   {metadata('java/lang/Double')}
  0x00007fd761114b85: jne     0x7fd761114b98          <-------- HERE!!!
  0x00007fd761114b87: mov     [=14=]x1,%eax
  0x00007fd761114b8c: add     [=14=]x10,%rsp
  0x00007fd761114b90: pop     %rbp
  0x00007fd761114b91: test    %eax,0x16774469(%rip)  ;   {poll_return}
  0x00007fd761114b97: retq
  0x00007fd761114b98: xor     %eax,%eax
  0x00007fd761114b9a: jmp     0x7fd761114b8c          <------- HERE!!!
  0x00007fd761114b9c: mov     [=14=]xfffffff4,%esi
  0x00007fd761114ba1: nop
  0x00007fd761114ba3: callq   0x7fd7610051a0    ; OopMap{off=72}
                                                ;*instanceof
                                                ; - com.get.intent.App::insOf@3 (line 24)
                                                ;   {runtime_call}
  0x00007fd761114ba8: callq   0x7fd776591a20    ;*instanceof
                                                ; - com.get.intent.App::insOf@3 (line 24)
                                                ;   {runtime_call}

省略了大量 hlt 说明。

据我所知,instance of 大约是十条汇编指令,有两次跳转(jnejmp)。跳跃有点混乱。为什么我们需要它们?

问题: Java instance of 真的这么快吗?

好吧,您正在涉足反汇编,因此您可能不得不重新构建那些跳转所代表的功能。使用源代码可用且具有调试符号的 JVM 可能是个好主意。

仅基于 instanceof 的语义,我认为这些跳转所做的是对超类递归执行相同的测试(因为基本上 instanceof 可以用功能伪代码编写为 instanceof(object, class) = class != null and (object.class == class or instanceof(object.class.superclass, class) or instanceof(any object.class.interface, class))

是的,大多数情况下都这么快,包括像您这样的微不足道的情况。它首先乐观地检查类型的确切命中(在您的情况下为 Double),然后回退到慢速分支,这将悲观地调用运行时,因为可能需要遍历 class 层次结构。但是,在 Double 的情况下,编译器知道系统中不存在 Double 的子 class,因此慢分支很简单 "false".

   mov     0x8(%r11),%r10d    ; read the klass ptr
   cmp     [=10=]x20002192,%r10d  ;   klass for 'java/lang/Double'
   jne     NOT_EQUAL          ; not equal? slow branch
   mov     [=10=]x1,%eax          ; return value = "true"
RETURN:
   add     [=10=]x10,%rsp         ; epilog
   pop     %rbp
   test    %eax,0x16774469(%rip) 
   retq
NOT_EQUAL:
   xor     %eax,%eax          ; return value = "false"
   jmp     RETURN