weakCompareAndSwap 与 compareAndSwap
weakCompareAndSwap vs compareAndSwap
这个问题不是关于它们之间的区别 - 我知道什么是虚假故障以及为什么它会发生在 LL/SC 上。我的问题是,如果我使用的是 intel x86 并使用 java-9(内部版本 149),为什么它们的汇编代码之间存在差异?
public class WeakVsNonWeak {
static jdk.internal.misc.Unsafe UNSAFE = jdk.internal.misc.Unsafe.getUnsafe();
public static void main(String[] args) throws NoSuchFieldException, SecurityException {
Holder h = new Holder();
h.setValue(33);
Class<?> holderClass = Holder.class;
long valueOffset = UNSAFE.objectFieldOffset(holderClass.getDeclaredField("value"));
int result = 0;
for (int i = 0; i < 30_000; ++i) {
result = strong(h, valueOffset);
}
System.out.println(result);
}
private static int strong(Holder h, long offset) {
int sum = 0;
for (int i = 33; i < 11_000; ++i) {
boolean result = UNSAFE.weakCompareAndSwapInt(h, offset, i, i + 1);
if (!result) {
sum++;
}
}
return sum;
}
public static class Holder {
private int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
}
运行:
java -XX:-TieredCompilation
-XX:CICompilerCount=1
-XX:+UnlockDiagnosticVMOptions
-XX:+PrintIntrinsics
-XX:+PrintAssembly
--add-opens java.base/jdk.internal.misc=ALL-UNNAMED
WeakVsNonWeak
compareAndSwapInt(相关部分)的输出:
0x0000000109f0f4b8: movabs [=12=]x111927c18,%rsi ; {metadata({method} {0x0000000111927c18} 'compareAndSwapInt' '(Ljava/lang/Object;JII)Z' in 'jdk/internal/misc/Unsafe')}
0x0000000109f0f4c2: mov %r15,%rdi
0x0000000109f0f4c5: test [=12=]xf,%esp
0x0000000109f0f4cb: je 0x0000000109f0f4e3
0x0000000109f0f4d1: sub [=12=]x8,%rsp
0x0000000109f0f4d5: callq 0x00000001098569d2 ; {runtime_call SharedRuntime::dtrace_method_entry(JavaThread*, Method*)}
0x0000000109f0f4da: add [=12=]x8,%rsp
0x0000000109f0f4de: jmpq 0x0000000109f0f4e8
0x0000000109f0f4e3: callq 0x00000001098569d2 ; {runtime_call SharedRuntime::dtrace_method_entry(JavaThread*, Method*)}
0x0000000109f0f4e8: pop %r9
0x0000000109f0f4ea: pop %r8
0x0000000109f0f4ec: pop %rcx
0x0000000109f0f4ed: pop %rdx
0x0000000109f0f4ee: pop %rsi
0x0000000109f0f4ef: lea 0x210(%r15),%rdi
0x0000000109f0f4f6: movl [=12=]x4,0x288(%r15)
0x0000000109f0f501: callq 0x00000001098fee40 ; {runtime_call Unsafe_CompareAndSwapInt(JNIEnv_*, _jobject*, _jobject*, long, int, int)}
0x0000000109f0f506: vzeroupper
0x0000000109f0f509: and [=12=]xff,%eax
0x0000000109f0f50f: setne %al
0x0000000109f0f512: movl [=12=]x5,0x288(%r15)
0x0000000109f0f51d: lock addl [=12=]x0,-0x40(%rsp)
0x0000000109f0f523: cmpl [=12=]x0,-0x3f04dd(%rip) # 0x0000000109b1f050
weakCompareAndSwapInt的输出:
0x000000010b698840: sub [=13=]x18,%rsp
0x0000010b698847: mov %rbp,0x10(%rsp)
0x000000010b69884c: mov %r8d,%eax
0x000000010b69884f: lock cmpxchg %r9d,(%rdx,%rcx,1)
0x000000010b698855: sete %r11b
0x000000010b698859: movzbl %r11b,%r11d ;*invokevirtual compareAndSwapInt {reexecute=0 rethrow=0 return_oop=0}
; - jdk.internal.misc.Unsafe::weakCompareAndSwapInt@7 (line 1369)
到目前为止,我还不够全面,无法理解整个输出,但绝对可以看出 lock addl 和 lock cmpxchg.
之间的区别
编辑
彼得的回答让我开始思考。让我们看看 compareAndSwap 是否是内部调用:
-XX:+PrintIntrinsics -XX:-PrintAssembly
@ 7 jdk.internal.misc.Unsafe::compareAndSwapInt (0 bytes) (intrinsic)
@ 20 jdk.internal.misc.Unsafe::weakCompareAndSwapInt (11 bytes) (intrinsic).
然后运行例子两次with/without:
-XX:DisableIntrinsic=_compareAndSwapInt
这有点奇怪,输出完全相同(完全相同的指令),唯一的区别是使用 enable intrinsic 我得到这样的调用:
0x000000010c23e355: callq 0x00000001016569d2 ; {runtime_call SharedRuntime::dtrace_method_entry(JavaThread*, Method*)}
0x000000010c23e381: callq 0x00000001016fee40 ; {runtime_call Unsafe_CompareAndSwapInt(JNIEnv_*, _jobject*, _jobject*, long, int, int)}
并禁用:
0x00000001109322d5: callq 0x0000000105c569d2 ; {runtime_call _ZN13SharedRuntime19dtrace_method_entryEP10JavaThreadP6Method}
0x00000001109322e3: callq 0x0000000105c569d2 ; {runtime_call _ZN13SharedRuntime19dtrace_method_entryEP10JavaThreadP6Method}
这很有趣,内在代码不应该有所不同吗?
EDIT-2 the8472 也有意义。
lock addl 是 mfence 的替代品,据我所知,它刷新了 x86 上的 StoreBuffer,它与可见性有关而不是原子性。在此条目之前,是:
0x00000001133db6f6: movl [=17=]x4,0x288(%r15)
0x00000001133db701: callq 0x00000001060fee40 ; {runtime_call Unsafe_CompareAndSwapInt(JNIEnv_*, _jobject*, _jobject*, long, int, int)}
0x00000001133db706: vzeroupper
0x00000001133db709: and [=17=]xff,%eax
0x00000001133db70f: setne %al
0x00000001133db712: movl [=17=]x5,0x288(%r15)
0x00000001133db71d: lock addl [=17=]x0,-0x40(%rsp)
0x00000001133db723: cmpl [=17=]x0,-0xd0bc6dd(%rip) # 0x000000010631f050
; {external_word}
如果你看 here is will delegate to another native call to Atomic:: cmpxchg 似乎是在原子地进行交换。
为什么这不能替代直接 lock cmpxchg 对我来说是个谜。
在第一种情况下,使用了本地方法。代码尚未优化或不是内在代码。
在第二种情况下,内部函数已用于内联所需的程序集,而不是调用 JNI 方法。虽然这两种情况都会这样做,但我想不会。
我相信 lock addl
不是原子操作本身,而是 store-load barrier implementation。原子发生在 callq
.
因为您已经使用 PrintIntrinsics
登录,您应该检查它是否真的被内化了。
TL;DR 你在汇编输出中看错了地方。
compareAndSwapInt
和 weakCompareAndSwapInt
调用 都编译为 完全相同的 x86-64 上的 ASM 序列.但是,方法 本身的编译方式不同(但这通常无关紧要)。
source code中compareAndSwapInt
和weakCompareAndSwapInt
的定义不同。前者是native方法,后者是Java方法
@HotSpotIntrinsicCandidate
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
@HotSpotIntrinsicCandidate
public final boolean weakCompareAndSwapInt(Object o, long offset,
int expected,
int x) {
return compareAndSwapInt(o, offset, expected, x);
}
您看到的是这些独立方法是如何编译的。本机方法编译为调用相应 C 函数的存根。但这不是在快速路径中运行的。
内部方法是那些调用被替换为 HotSpot-specific 内联实现的方法。 注意:调用被替换,但不是方法本身。
如果您查看 WeakVsNonWeak.strong
方法的汇编输出,您会发现它包含 lock cmpxchg
指令,无论它调用 UNSAFE.compareAndSwapInt
还是 UNSAFE.weakCompareAndSwapInt
.
0x000001bd76170c44: lock cmpxchg %ecx,(%r11)
0x000001bd76170c49: sete %r10b
0x000001bd76170c4d: movzbl %r10b,%r10d ;*invokevirtual compareAndSwapInt
; - WeakVsNonWeak::strong@25 (line 23)
; - WeakVsNonWeak::main@46 (line 14)
0x0000024f56af1097: lock cmpxchg %r11d,(%r8)
0x0000024f56af109c: sete %r10b
0x0000024f56af10a0: movzbl %r10b,%r10d ;*invokevirtual weakCompareAndSwapInt
; - WeakVsNonWeak::strong@25 (line 23)
; - WeakVsNonWeak::main@46 (line 14)
一旦main方法为JIT-compiled,单机版的Unsafe.*方法将不会被直接调用
这个问题不是关于它们之间的区别 - 我知道什么是虚假故障以及为什么它会发生在 LL/SC 上。我的问题是,如果我使用的是 intel x86 并使用 java-9(内部版本 149),为什么它们的汇编代码之间存在差异?
public class WeakVsNonWeak {
static jdk.internal.misc.Unsafe UNSAFE = jdk.internal.misc.Unsafe.getUnsafe();
public static void main(String[] args) throws NoSuchFieldException, SecurityException {
Holder h = new Holder();
h.setValue(33);
Class<?> holderClass = Holder.class;
long valueOffset = UNSAFE.objectFieldOffset(holderClass.getDeclaredField("value"));
int result = 0;
for (int i = 0; i < 30_000; ++i) {
result = strong(h, valueOffset);
}
System.out.println(result);
}
private static int strong(Holder h, long offset) {
int sum = 0;
for (int i = 33; i < 11_000; ++i) {
boolean result = UNSAFE.weakCompareAndSwapInt(h, offset, i, i + 1);
if (!result) {
sum++;
}
}
return sum;
}
public static class Holder {
private int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
}
运行:
java -XX:-TieredCompilation
-XX:CICompilerCount=1
-XX:+UnlockDiagnosticVMOptions
-XX:+PrintIntrinsics
-XX:+PrintAssembly
--add-opens java.base/jdk.internal.misc=ALL-UNNAMED
WeakVsNonWeak
compareAndSwapInt(相关部分)的输出:
0x0000000109f0f4b8: movabs [=12=]x111927c18,%rsi ; {metadata({method} {0x0000000111927c18} 'compareAndSwapInt' '(Ljava/lang/Object;JII)Z' in 'jdk/internal/misc/Unsafe')}
0x0000000109f0f4c2: mov %r15,%rdi
0x0000000109f0f4c5: test [=12=]xf,%esp
0x0000000109f0f4cb: je 0x0000000109f0f4e3
0x0000000109f0f4d1: sub [=12=]x8,%rsp
0x0000000109f0f4d5: callq 0x00000001098569d2 ; {runtime_call SharedRuntime::dtrace_method_entry(JavaThread*, Method*)}
0x0000000109f0f4da: add [=12=]x8,%rsp
0x0000000109f0f4de: jmpq 0x0000000109f0f4e8
0x0000000109f0f4e3: callq 0x00000001098569d2 ; {runtime_call SharedRuntime::dtrace_method_entry(JavaThread*, Method*)}
0x0000000109f0f4e8: pop %r9
0x0000000109f0f4ea: pop %r8
0x0000000109f0f4ec: pop %rcx
0x0000000109f0f4ed: pop %rdx
0x0000000109f0f4ee: pop %rsi
0x0000000109f0f4ef: lea 0x210(%r15),%rdi
0x0000000109f0f4f6: movl [=12=]x4,0x288(%r15)
0x0000000109f0f501: callq 0x00000001098fee40 ; {runtime_call Unsafe_CompareAndSwapInt(JNIEnv_*, _jobject*, _jobject*, long, int, int)}
0x0000000109f0f506: vzeroupper
0x0000000109f0f509: and [=12=]xff,%eax
0x0000000109f0f50f: setne %al
0x0000000109f0f512: movl [=12=]x5,0x288(%r15)
0x0000000109f0f51d: lock addl [=12=]x0,-0x40(%rsp)
0x0000000109f0f523: cmpl [=12=]x0,-0x3f04dd(%rip) # 0x0000000109b1f050
weakCompareAndSwapInt的输出:
0x000000010b698840: sub [=13=]x18,%rsp
0x0000010b698847: mov %rbp,0x10(%rsp)
0x000000010b69884c: mov %r8d,%eax
0x000000010b69884f: lock cmpxchg %r9d,(%rdx,%rcx,1)
0x000000010b698855: sete %r11b
0x000000010b698859: movzbl %r11b,%r11d ;*invokevirtual compareAndSwapInt {reexecute=0 rethrow=0 return_oop=0}
; - jdk.internal.misc.Unsafe::weakCompareAndSwapInt@7 (line 1369)
到目前为止,我还不够全面,无法理解整个输出,但绝对可以看出 lock addl 和 lock cmpxchg.
之间的区别编辑 彼得的回答让我开始思考。让我们看看 compareAndSwap 是否是内部调用:
-XX:+PrintIntrinsics -XX:-PrintAssembly
@ 7 jdk.internal.misc.Unsafe::compareAndSwapInt (0 bytes) (intrinsic)
@ 20 jdk.internal.misc.Unsafe::weakCompareAndSwapInt (11 bytes) (intrinsic).
然后运行例子两次with/without:
-XX:DisableIntrinsic=_compareAndSwapInt
这有点奇怪,输出完全相同(完全相同的指令),唯一的区别是使用 enable intrinsic 我得到这样的调用:
0x000000010c23e355: callq 0x00000001016569d2 ; {runtime_call SharedRuntime::dtrace_method_entry(JavaThread*, Method*)}
0x000000010c23e381: callq 0x00000001016fee40 ; {runtime_call Unsafe_CompareAndSwapInt(JNIEnv_*, _jobject*, _jobject*, long, int, int)}
并禁用:
0x00000001109322d5: callq 0x0000000105c569d2 ; {runtime_call _ZN13SharedRuntime19dtrace_method_entryEP10JavaThreadP6Method}
0x00000001109322e3: callq 0x0000000105c569d2 ; {runtime_call _ZN13SharedRuntime19dtrace_method_entryEP10JavaThreadP6Method}
这很有趣,内在代码不应该有所不同吗?
EDIT-2 the8472 也有意义。
lock addl 是 mfence 的替代品,据我所知,它刷新了 x86 上的 StoreBuffer,它与可见性有关而不是原子性。在此条目之前,是:
0x00000001133db6f6: movl [=17=]x4,0x288(%r15)
0x00000001133db701: callq 0x00000001060fee40 ; {runtime_call Unsafe_CompareAndSwapInt(JNIEnv_*, _jobject*, _jobject*, long, int, int)}
0x00000001133db706: vzeroupper
0x00000001133db709: and [=17=]xff,%eax
0x00000001133db70f: setne %al
0x00000001133db712: movl [=17=]x5,0x288(%r15)
0x00000001133db71d: lock addl [=17=]x0,-0x40(%rsp)
0x00000001133db723: cmpl [=17=]x0,-0xd0bc6dd(%rip) # 0x000000010631f050
; {external_word}
如果你看 here is will delegate to another native call to Atomic:: cmpxchg 似乎是在原子地进行交换。
为什么这不能替代直接 lock cmpxchg 对我来说是个谜。
在第一种情况下,使用了本地方法。代码尚未优化或不是内在代码。
在第二种情况下,内部函数已用于内联所需的程序集,而不是调用 JNI 方法。虽然这两种情况都会这样做,但我想不会。
我相信 lock addl
不是原子操作本身,而是 store-load barrier implementation。原子发生在 callq
.
因为您已经使用 PrintIntrinsics
登录,您应该检查它是否真的被内化了。
TL;DR 你在汇编输出中看错了地方。
compareAndSwapInt
和 weakCompareAndSwapInt
调用 都编译为 完全相同的 x86-64 上的 ASM 序列.但是,方法 本身的编译方式不同(但这通常无关紧要)。
source code中
compareAndSwapInt
和weakCompareAndSwapInt
的定义不同。前者是native方法,后者是Java方法@HotSpotIntrinsicCandidate public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x); @HotSpotIntrinsicCandidate public final boolean weakCompareAndSwapInt(Object o, long offset, int expected, int x) { return compareAndSwapInt(o, offset, expected, x); }
您看到的是这些独立方法是如何编译的。本机方法编译为调用相应 C 函数的存根。但这不是在快速路径中运行的。
内部方法是那些调用被替换为 HotSpot-specific 内联实现的方法。 注意:调用被替换,但不是方法本身。
如果您查看
WeakVsNonWeak.strong
方法的汇编输出,您会发现它包含lock cmpxchg
指令,无论它调用UNSAFE.compareAndSwapInt
还是UNSAFE.weakCompareAndSwapInt
.0x000001bd76170c44: lock cmpxchg %ecx,(%r11) 0x000001bd76170c49: sete %r10b 0x000001bd76170c4d: movzbl %r10b,%r10d ;*invokevirtual compareAndSwapInt ; - WeakVsNonWeak::strong@25 (line 23) ; - WeakVsNonWeak::main@46 (line 14) 0x0000024f56af1097: lock cmpxchg %r11d,(%r8) 0x0000024f56af109c: sete %r10b 0x0000024f56af10a0: movzbl %r10b,%r10d ;*invokevirtual weakCompareAndSwapInt ; - WeakVsNonWeak::strong@25 (line 23) ; - WeakVsNonWeak::main@46 (line 14)
一旦main方法为JIT-compiled,单机版的Unsafe.*方法将不会被直接调用