编程语言
首页 > 编程语言> > java – 在volatile的上下文中分析JIT生成的x86输出

java – 在volatile的上下文中分析JIT生成的x86输出

作者:互联网

我正在撰写与Deep understanding of volatile in Java相关的这篇文章

public class Main {
    private int x;
    private volatile int g;


    public void actor1(){
       x = 1;
       g = 1;
    }


    public void actor2(){
       put_on_screen_without_sync(g);
       put_on_screen_without_sync(x);
    }
}

现在,我正在分析JIT为上面的代码生成了什么.根据我在上一篇文章中的讨论,我们知道输出1,0是不可能的,因为:

写入volatile v会导致v之前的每个操作都会导致a在v可见之前可见(将被刷新到内存中).

   .................(I removed not important body of method).....

  0x00007f42307d9d5e: c7460c01000000     (1) mov       dword ptr [rsi+0ch],1h
                                                ;*putfield x
                                                ; - package.Main::actor1@2 (line 14)

  0x00007f42307d9d65: bf01000000          (2) mov       edi,1h
  0x00007f42307d9d6a: 897e10              (3) mov       dword ptr [rsi+10h],edi
  0x00007f42307d9d6d: f083042400          (4) lock add  dword ptr [rsp],0h
                                                ;*putfield g
                                                ; - package.Main::actor1@7 (line 15)

  0x00007f42307d9d72: 4883c430            add       rsp,30h
  0x00007f42307d9d76: 5d                  pop       rbp
  0x00007f42307d9d77: 850583535116        test      dword ptr [7f4246cef100h],eax
                                                ;   {poll_return}
  0x00007f42307d9d7d: c3                  ret

我是否正确理解它是否有效,因为x86无法使StoreStore重新排序?如果它可能需要额外的内存屏障,是吗?

优秀之后@ Eugene的回答:

06002

在这里,我明白你的意思 – 很明显:下面(后面)volatile read(int tmp = i)的每个动作都不会被重新排序.

06003

在这里,你又增加了一个障碍.它确保我们不会使用int tmp = i重新排序任何操作.但是,为什么它很重要?为什么我有疑虑?据我所知,挥发性负荷保证:

在易失性负载可见之前,不会重新排序易失性负载后的每个动作.

我看到你写道:

There needs to be a sequential consistency

但是,我不明白为什么需要顺序一致性.

解决方法:

有几件事情,首先会被冲到记忆中 – 这是非常错误的.它几乎从不与主存储器冲洗 – 它通常将StoreBuffer排到L1并且由缓存一致性协议来同步所有缓存之间的数据,但是如果你更容易理解这些概念,那就没关系 – 只是知道这有点不同而且更快.

这是一个很好的问题,为什么[StoreLoad]确实存在,也许这会清理一些事情. volatile实际上都是关于围栏的,这里是一些在一些易变操作的情况下会插入障碍的例子.例如,我们有一个不稳定的负载:

  // i is some shared volatile field
  int tmp = i; // volatile load of "i"
  // [LoadLoad|LoadStore]

注意这里的两个障碍是LoadStore和LoadLoad;用简单的英语表示在易失性加载/读取之后出现的任何加载和存储都不能“向上移动”屏障,它们不能被重新排序“高于”易失性负载.

这是volatile商店的例子.

 // "i" is a shared volatile variable
 // [StoreStore|LoadStore]
 i = tmp; // volatile store

这意味着任何加载和存储都不能在加载存储本身“下方”.

这基本上构建了发生在之前的关系,易失性负载是获取负载,而易失性存储是释放存储(这也与如何实现Store和Load cpu缓冲区有关,但它几乎超出了问题的范围) .

如果你仔细想想它,那么我们对于一般情况下我们所知道的事情就会非常有意义;它表示,一旦挥发性负载观察到易失性存储,也会观察到易失性存储之前的所有内容,这与内存障碍相当.现在有意义的是,当一个易失性存储发生时,它上面的所有东西都不能超越它,一旦一个易失性负载发生,它下面的所有东西都不能超过它,否则这种情况发生 – 之前会被破坏.

但那不是它,还有更多.需要有连续的一致性,这就是为什么任何理智的实现都能保证挥发物本身不被重新排序,因此插入了两个更多的围栏:

 // any store of some other volatile
 // can not be reordered with this volatile load
 // [StoreLoad] -- this one
 int tmp = i; // volatile load of a shared variable "i"
 // [LoadStore|LoadLoad]

还有一个:

// [StoreStore|LoadStore]
i = tmp; // volatile store
// [StoreLoad] -- and this one

现在,事实证明,在x86上,4个内存屏障中有3个是免费的 – 因为它是一个强大的内存模型.唯一需要实现的是StoreLoad.在其他CPU上,例如ARM,lwsycn是一个使用的指令 – 但我不太了解它们.

通常mfence对于x86上的StoreLoad是一个很好的选择,但同样的事情是通过锁定添加(AFAIK以更便宜的方式)保证,这就是你在那里看到的原因.基本上这就是StoreLoad的障碍.是的 – 你的最后一句是正确的,对于较弱的内存模型 – 将需要StoreStore屏障.在旁注上,这是在通过构造函数中的最终字段安全地发布引用时使用的内容.退出构造函数后,插入了两个fence:LoadStore和StoreStore.

把这一切都拿出来 – 只要不破坏任何规则,JVM可以自由地忽略这些:Aleksey Shipilev对此有很好的讨论.

编辑

假设你有这种情况:

[StoreStore|LoadStore]
int x = 4; // volatile store of a shared "x" variable

int y = 3; // non-volatile store of shared variable "y"

int z = x; // volatile load
[LoadLoad|LoadStore]

基本上没有障碍可以防止易失性存储器与易失性负载一起重新排序(即:首先执行易失性负载)并且这会明显引起问题;因此违反了顺序一致性.

在易失性负载可见之前,不会重新排序易失性负载之后,你有点遗漏了这一点btw(如果我没有记错)通过每个动作.易失性本身无法重新排序 – 其他操作可以自由重新排序.让我给你举个例子:

 int tmp = i; // volatile load of a shared variable "i"
 // [LoadStore|LoadLoad]

 int x = 3; // plain store
 int y = 4; // plain store

最后两个操作x = 3和y = 4完全可以自由重新排序,它们不能浮动在volatile之上,但它们可以通过它们自己重新排序.上面的例子是完全合法的:

 int tmp = i; // volatile load
 // [LoadStore|LoadLoad]

 // see how they have been inverted here...
 int y = 4; // plain store
 int x = 3; // plain store

标签:memory-barriers,java,volatile,jvm
来源: https://codeday.me/bug/20190926/1820237.html