weakCompareAndSwap против compareAndSwap
этот вопрос не о разнице между ними - я знаю, что такое ложный сбой и почему это происходит на LL / SC. Мой вопрос: если я на intel x86 и использую java-9 (build 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 x111927c18,%rsi ; {metadata({method} {0x0000000111927c18} 'compareAndSwapInt' '(Ljava/lang/Object;JII)Z' in 'jdk/internal/misc/Unsafe')}
0x0000000109f0f4c2: mov %r15,%rdi
0x0000000109f0f4c5: test xf,%esp
0x0000000109f0f4cb: je 0x0000000109f0f4e3
0x0000000109f0f4d1: sub x8,%rsp
0x0000000109f0f4d5: callq 0x00000001098569d2 ; {runtime_call SharedRuntime::dtrace_method_entry(JavaThread*, Method*)}
0x0000000109f0f4da: add 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 x4,0x288(%r15)
0x0000000109f0f501: callq 0x00000001098fee40 ; {runtime_call Unsafe_CompareAndSwapInt(JNIEnv_*, _jobject*, _jobject*, long, int, int)}
0x0000000109f0f506: vzeroupper
0x0000000109f0f509: and xff,%eax
0x0000000109f0f50f: setne %al
0x0000000109f0f512: movl x5,0x288(%r15)
0x0000000109f0f51d: lock addl x0,-0x40(%rsp)
0x0000000109f0f523: cmpl x0,-0x3f04dd(%rip) # 0x0000000109b1f050
выход weakCompareAndSwapInt:
0x000000010b698840: sub 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)
Я далеко не достаточно универсален, чтобы понять весь выход, но определенно может видеть разницу между блокировка addl и блокировка cmpxchg.
редактировать Ответ Питера заставил меня задуматься. Посмотрим, будет ли compareAndSwap внутренним вызовом:
- XX: + PrintIntrinsics-XX: - PrintAssembly
@ 7 jdk.internal.misc.Unsafe::compareAndSwapInt (0 bytes) (intrinsic)
@ 20 jdk.internal.misc.Unsafe::weakCompareAndSwapInt (11 bytes) (intrinsic).
а затем запустите пример дважды с/без:
- 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 тоже имеет смысл.
блокировка addl замена для mfence это сбрасывает StoreBuffer на x86, насколько я знаю, и это связано с видимостью, а не атомарностью. Прямо перед этой записью:
0x00000001133db6f6: movl x4,0x288(%r15)
0x00000001133db701: callq 0x00000001060fee40 ; {runtime_call Unsafe_CompareAndSwapInt(JNIEnv_*, _jobject*, _jobject*, long, int, int)}
0x00000001133db706: vzeroupper
0x00000001133db709: and xff,%eax
0x00000001133db70f: setne %al
0x00000001133db712: movl x5,0x288(%r15)
0x00000001133db71d: lock addl x0,-0x40(%rsp)
0x00000001133db723: cmpl x0,-0xd0bc6dd(%rip) # 0x000000010631f050
; {external_word}
если вы посмотрите здесь is делегирует другому родному вызов Atomic:: cmpxchg это, кажется, делает обмен атомарно.
почему это не замена прямой cmpxchg замок для меня загадка.
3 ответов
TL; DR вы смотрите на неправильное место в выходе сборки.
и compareAndSwapInt
и weakCompareAndSwapInt
звонки компилируются в точно так же последовательность ASM на x86-64. Однако методы сами компилируются по-разному (но обычно это не имеет значения).
-
определение
compareAndSwapInt
иweakCompareAndSwapInt
на исходный код разное. Этот первый является собственным методом, в то время как последний является методом 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. Примечание: на звонки несколько заменили, но не сами методы.
-
если вы посмотрите на результат сборки
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)
как только основной метод JIT-скомпилирован, автономная версия небезопасна.* методы не будут вызываться напрямую.
в первом случае используется собственный метод. Либо код не был оптимизирован, либо он не является встроенным.
во втором случае для встроенной сборки, а не для вызова метода JNI, используется встроенная сборка. Я бы, хотя оба случая сделали бы это, но я думаю, нет.
Я считаю lock addl
не сам атомарный op, а магазин-реализация барьера нагрузки. атомное происходит в callq
.
Так как вы уже входите в систему с PrintIntrinsics
вы должны проверить, действительно ли он получает intrinsified.