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. Однако методы сами компилируются по-разному (но обычно это не имеет значения).

  1. определение 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);
    }
    
  2. то, что вы видели, как эти автономной методы. Собственный метод компилируется в заглушку, которая вызывает соответствующую функцию C. Но это не то, что идет быстрым путем.

  3. встроенные методы - это те, которые вызовы заменяются встроенной реализацией HotSpot. Примечание: на звонки несколько заменили, но не сами методы.

  4. если вы посмотрите на результат сборки 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.