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 大约是十条汇编指令,有两次跳转(jne
、jmp
)。跳跃有点混乱。为什么我们需要它们?
问题: 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
我正在尝试测量 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 大约是十条汇编指令,有两次跳转(jne
、jmp
)。跳跃有点混乱。为什么我们需要它们?
问题: 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