【问题标题】:weakCompareAndSwap vs compareAndSwap弱CompareAndSwap vs compareAndSwap
【发布时间】:2016-12-29 14:46:14
【问题描述】:

这个问题不是关于它们之间的区别 - 我知道什么是虚假故障以及它为什么会发生在 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 $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::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).

然后在有/无的情况下运行该示例两次:

-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 也很有意义。

lock addlmfence 的替代品,据我所知,它在 x86 上刷新 StoreBuffer,它确实与可见性而不是原子性有关。在此条目之前,是:

 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 似乎正在原子地进行交换。

为什么这不能替代直接 lock cmpxchg 对我来说是个谜。

【问题讨论】:

  • 根据您的编辑和来自不同优化级别的大量装配示例,您实际上在问什么并不十分清楚。
  • 所以sun.misc.Unsafe 仍然没有消失,而是移到了另一个包jdk.internal.misc,证明这实际上不是兼容性问题,让该类保持活力?
  • @Holger 没动,现在有两个版本。正如 Shipilev 所说, sun.misc.Unsafe 将被删除 - 这次是肯定的。在 sun.misc.Unsafe 曾经有用但现在已过时的 other 位置有多项增强功能(如 AtomicFieldUpdater)。他们甚至直接在 Unsafe 中添加了释放/获取语义!
  • 我只是认为VarHandle 应该正式处理所有这些东西,现在我看到一个Unsafe 类,与Java 8 版本相比,它显然甚至得到了扩展。这看起来不像摆脱它......
  • @Holger sun.misc.Unsafe 将被删除,而不是第二个。他们仍然需要一种方法来暴露 Unsafe 并使其非常安全。 VarHandle 是 jdk.internal.misc.Unsafe 公开的安全 PUBLIC api。

标签: java jvm jvm-hotspot compare-and-swap java-9


【解决方案1】:

TL;DR您在程序集输出中查看了错误的位置。

compareAndSwapIntweakCompareAndSwapInt 调用 都编译为 完全相同 x86-64 上的 ASM 序列。但是,方法本身的编译方式不同(但通常并不重要)。

  1. source codecompareAndSwapIntweakCompareAndSwapInt的定义不同。前者是原生方法,后者是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)
    

    一旦 main 方法被 JIT 编译,独立版本的 Unsafe.* 方法将不会被直接调用。

【讨论】:

  • 你是对的:如果没有一些适当的经验(比如我),很难完整地阅读输出。你的解释太棒了!我在代码中看到和显示的是从 c2 编译中输出的 individual 方法,它 != 内部代码;编译 strong 方法后,使用 UNSAFE.compareAndSwapIntUNSAFE.weakCompareAndSwapInt 会产生相同的输出,这意味着它们的内在代码是相同的。
【解决方案2】:

在第一种情况下,使用的是本机方法。要么代码没有经过优化,要么不是内在代码。

在第二种情况下,内部函数已用于内联所需的程序集,而不是调用 JNI 方法。虽然两种情况都会这样做,但我想不会。

【讨论】:

  • 确实你是可能正确的,但我不知道为什么。查看编辑
  • @Eugene 我同意它出现倒退。内在应该有 mov 而非内在应该有 callq
  • 这不是重点。 compareAndSwap intrinsiccompareAndSwap non-intrinsics 与 callq 的函数不同。我期待更多
  • @Eugene 我很确定您可以忽略 dtrace 方法入口调用。这些不应该做任何事情(除了帮助跟踪)
【解决方案3】:

我相信lock addl 不是原子操作本身,而是store-load barrier implementation。原子发生在callq

由于您已经使用 PrintIntrinsics 登录,您应该检查它是否真的被内化了。

【讨论】:

  • 确实你也是对的(见EDIT-2),但它没有回答主要问题。尽管如此,还是感谢您的意见。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-19
  • 2012-02-14
  • 1970-01-01
相关资源
最近更新 更多