Hotspot JVM中AtomicInteger.lazySet()的解释器和JIT编译器的实现区别
The implementation difference between the interperter and the JIT compiler for AtomicInteger.lazySet() in the Hotspot JVM
关于AtomicInteger.lazySet()的背景,请参考AtomicInteger lazySet vs. set的现有讨论。
所以根据AtomicInteger.lazySet()的语义,在x86上CPU,AtomicInteger.lazySet()相当于对AotmicInteger的值进行一次正常的写操作,因为x86内存模型保证写操作之间的顺序。
但是,AtomicInteger.lazySet() 的运行时行为在 JDK 8 Hotspot JVM 中的解释器和 JIT 编译器(特别是 C2 编译器)之间是不同的,这让我很困惑。
首先,创建一个简单的 Java 应用程序进行演示。
import java.util.concurrent.atomic.AtomicInteger;
public class App {
public static void main (String[] args) throws Exception {
AtomicInteger i = new AtomicInteger(0);
i.lazySet(1);
System.out.println(i.get());
}
}
然后转储C2编译器提供的instrinc方法中AtomicInteger.lazySet()的指令:
$ java -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:-TieredCompilation -XX:CompileCommand=print,*AtomicInteger.lazySet App
...
0x00007f1bd927214c: mov %edx,0xc(%rsi) ;*invokevirtual putOrderedInt
; - java.util.concurrent.atomic.AtomicInteger::lazySet@8 (line 110)
如您所见,操作符合预期,正常写入。
然后,使用 GDB 跟踪 AtomicInteger.lazySet() 的解释器的运行时行为。
$ gdb --args java App
(gdb) b Unsafe_SetOrderedInt
0x7ffff69ae836 callq 0x7ffff69b6642 <OrderAccess::release_store_fence(int volatile*, int)>
0x7ffff69b6642:
push %rbp
mov %rsp,%rbp
mov %rdi,-0x8(%rbp)
mov %esi,-0xc(%rbp)
mov -0xc(%rbp),%eax
mov -0x8(%rbp),%rdx
xchg %eax,(%rdx) // the write operation
mov %eax,-0xc(%rbp)
nop
pop %rbp
retq
s 你可以看到,该操作实际上是一个 XCHG 指令,它具有隐式锁语义,这带来了 AtomicInteger.lazySet() 旨在消除的性能开销。
有谁知道为什么会有这样的差异?谢谢。
在解释器中优化一个很少使用的操作没有多大意义。这会增加开发和维护成本,但没有明显的好处。
HotSpot 中的常见做法是仅在 JIT 编译器(C2 或 C1+C2)中实施优化。解释器实现只是工作,它不需要很快,因为如果代码对性能敏感,无论如何它都会被 JIT 编译。
因此,在解释器中(即在慢速路径上),Unsafe_SetOrderedInt
与易失性写入完全相同。
关于AtomicInteger.lazySet()的背景,请参考AtomicInteger lazySet vs. set的现有讨论。
所以根据AtomicInteger.lazySet()的语义,在x86上CPU,AtomicInteger.lazySet()相当于对AotmicInteger的值进行一次正常的写操作,因为x86内存模型保证写操作之间的顺序。
但是,AtomicInteger.lazySet() 的运行时行为在 JDK 8 Hotspot JVM 中的解释器和 JIT 编译器(特别是 C2 编译器)之间是不同的,这让我很困惑。
首先,创建一个简单的 Java 应用程序进行演示。
import java.util.concurrent.atomic.AtomicInteger;
public class App {
public static void main (String[] args) throws Exception {
AtomicInteger i = new AtomicInteger(0);
i.lazySet(1);
System.out.println(i.get());
}
}
然后转储C2编译器提供的instrinc方法中AtomicInteger.lazySet()的指令:
$ java -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:-TieredCompilation -XX:CompileCommand=print,*AtomicInteger.lazySet App
...
0x00007f1bd927214c: mov %edx,0xc(%rsi) ;*invokevirtual putOrderedInt
; - java.util.concurrent.atomic.AtomicInteger::lazySet@8 (line 110)
如您所见,操作符合预期,正常写入。
然后,使用 GDB 跟踪 AtomicInteger.lazySet() 的解释器的运行时行为。
$ gdb --args java App
(gdb) b Unsafe_SetOrderedInt
0x7ffff69ae836 callq 0x7ffff69b6642 <OrderAccess::release_store_fence(int volatile*, int)>
0x7ffff69b6642:
push %rbp
mov %rsp,%rbp
mov %rdi,-0x8(%rbp)
mov %esi,-0xc(%rbp)
mov -0xc(%rbp),%eax
mov -0x8(%rbp),%rdx
xchg %eax,(%rdx) // the write operation
mov %eax,-0xc(%rbp)
nop
pop %rbp
retq
s 你可以看到,该操作实际上是一个 XCHG 指令,它具有隐式锁语义,这带来了 AtomicInteger.lazySet() 旨在消除的性能开销。
有谁知道为什么会有这样的差异?谢谢。
在解释器中优化一个很少使用的操作没有多大意义。这会增加开发和维护成本,但没有明显的好处。
HotSpot 中的常见做法是仅在 JIT 编译器(C2 或 C1+C2)中实施优化。解释器实现只是工作,它不需要很快,因为如果代码对性能敏感,无论如何它都会被 JIT 编译。
因此,在解释器中(即在慢速路径上),Unsafe_SetOrderedInt
与易失性写入完全相同。