2016-12-29 16 views
10

Этот вопрос не о различии между ними - я знаю, что такое ложный отказ и почему это происходит на LL/SC. Мой вопрос: если я на Intel x86 и используя java-9 (сборка 149), почему существует разница между их ассемблером?weakCompareAndSwap vs compareAndSwap

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 $0x111927c18,%rsi ; {metadata({method} {0x0000000111927c18} 'compareAndSwapInt' '(Ljava/lang/Object;JII)Z' in 'jdk/internal/misc/Unsafe')} 
    0x0000000109f0f4c2: mov %r15,%rdi 
    0x0000000109f0f4c5: test $0xf,%esp 
    0x0000000109f0f4cb: je  0x0000000109f0f4e3 
    0x0000000109f0f4d1: sub $0x8,%rsp 
    0x0000000109f0f4d5: callq 0x00000001098569d2 ; {runtime_call SharedRuntime::dtrace_method_entry(JavaThread*, Method*)} 
    0x0000000109f0f4da: add $0x8,%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 $0x4,0x288(%r15) 
    0x0000000109f0f501: callq 0x00000001098fee40 ; {runtime_call Unsafe_CompareAndSwapInt(JNIEnv_*, _jobject*, _jobject*, long, int, int)} 
    0x0000000109f0f506: vzeroupper 
    0x0000000109f0f509: and $0xff,%eax 
    0x0000000109f0f50f: setne %al 
    0x0000000109f0f512: movl $0x5,0x288(%r15) 
    0x0000000109f0f51d: lock addl $0x0,-0x40(%rsp) 
    0x0000000109f0f523: cmpl $0x0,-0x3f04dd(%rip)  # 0x0000000109b1f050 

Выход weakCompareAndSwapInt:

0x000000010b698840: sub $0x18,%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::[email protected] (line 1369) 

Я до сих пор не универсален, чтобы понять весь результат, но может определенно увидеть разницу между блокировкой addl и блокировкой cmpxchg.

EDIT Ответ Петра заставил меня думать. Давайте посмотрим, если будет сравнение с обменом характеристической вызов:

-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

Это сорт т странно, выход точно такие же (те же самые точные инструкции) с единственным отличием, что с позволяют присущий я получаю звонки, как это:

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 также имеет смысл.

замок приставной является заменой mfence, который промывает StoreBuffer на x86, насколько я знаю, и это имеет отношение к видимости и не атомарностью действительно. Непосредственно перед этой записью является:

0x00000001133db6f6: movl $0x4,0x288(%r15) 
0x00000001133db701: callq 0x00000001060fee40 ; {runtime_call Unsafe_CompareAndSwapInt(JNIEnv_*, _jobject*, _jobject*, long, int, int)} 
0x00000001133db706: vzeroupper 
0x00000001133db709: and $0xff,%eax 
0x00000001133db70f: setne %al 
0x00000001133db712: movl $0x5,0x288(%r15) 
0x00000001133db71d: lock addl $0x0,-0x40(%rsp) 
0x00000001133db723: cmpl $0x0,-0xd0bc6dd(%rip)  #  0x000000010631f050 
              ; {external_word} 

Если посмотреть here это будет передавать другую родную call to Atomic:: cmpxchg, который, кажется, делают своп атомарно.

Почему это не является заменой прямого замка cmpxchg для меня загадка.

+0

с вашими изменениями и многочисленными образцами сборки с разных уровней оптимизации не совсем понятно, что вы на самом деле спрашиваете. – the8472

+0

Так что 'sun.misc.Unsafe' все еще не ушел, но перешел в другой пакет' jdk.internal.misc', доказывая, что на самом деле это не проблема совместимости, которая поддерживает этот класс? – Holger

+0

@Holger Он не двигался, теперь есть две версии. как говорит Шипилев, sun.misc.Unsafe будет удален - на этот раз наверняка. Есть несколько улучшений в * других * местах, которые sun.misc.Unsafe используется для использования, которые теперь устарели (например, AtomicFieldUpdater). Они даже добавили релиз/приобретают семантику прямо в Unsafe! – Eugene

ответ

4

TL; DR Вы видите неправильное место в сборке.

Оба compareAndSwapInt и weakCompareAndSwapIntвызовы компилируются точно такой же последовательности ASM на x86-64. Однако, методы сами составлены по-разному (но обычно это не имеет значения).

  1. Определение compareAndSwapInt и weakCompareAndSwapInt в source code отличается. Первый - это собственный метод, а второй - метод 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::[email protected] (line 23) 
                   ; - WeakVsNonWeak::[email protected] (line 14) 
    
    0x0000024f56af1097: lock cmpxchg %r11d,(%r8) 
    0x0000024f56af109c: sete %r10b 
    0x0000024f56af10a0: movzbl %r10b,%r10d  ;*invokevirtual weakCompareAndSwapInt 
                   ; - WeakVsNonWeak::[email protected] (line 23) 
                   ; - WeakVsNonWeak::[email protected] (line 14) 
    

    После того, как основной метод JIT-компиляции, автономная версия небезопасным. * Методы не будут вызывать непосредственно.

+2

Вы правы: трудно прочитать результат в своей славе без какого-либо надлежащего опыта (например, я). Вы объяснения фантастические! то, что я видел и показывал в коде, - это * индивидуальный * метод, выводимый из компиляции c2, который: = внутренний код; когда метод 'strong' скомпилирован, использование' UNSAFE.compareAndSwapInt' или 'UNSAFE.weakCompareAndSwapInt' дает тот же результат, что и внутренний код, тот же. – Eugene

4

В первом случае используется собственный метод.Либо код не был оптимизирован, либо он не является внутренним.

Во втором случае внутренняя часть была использована для встраивания требуемой сборки, а не для вызова метода JNI. Я бы хотел, чтобы оба дела сделали это, но я думаю, нет.

+2

действительно вы, вероятно, правы, но я не уверен, почему. См. Edit – Eugene

+1

@Eugene. Я согласен, что он выглядит обратным. Внутренний должен иметь mov и non-intrinsic должен иметь callq –

+2

, это не является точкой. * compareAndSwap intrinsic * и * compareAndSwap non-intrinsics * отличается ** только ** в функциях от callq. Я ожидал намного больше – Eugene

4

Я считаю, что lock addl не является атомарным, а store-load barrier implementation. атом происходит в callq.

Поскольку вы уже регистрируетесь с помощью PrintIntrinsics, вы должны проверить, действительно ли он наследуется.

+0

действительно вы тоже правы (см. EDIT-2), но он не отвечает на главный вопрос. тем не менее, спасибо за ваш вклад. – Eugene