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

时间:2017-07-17 19:02:22

标签: java jvm volatile memory-barriers

我正在撰写与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是不可能的,因为:

写入易失v会导致a之前的每个操作v导致av之前显示 .................(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 (将被刷新到内存中)是可见的。

StoreStore

我是否正确理解它是否有效,因为x86无法重新排序 int tmp = i; // volatile load // [LoadStore] // [LoadLoad] ?如果它可能需要额外的内存屏障,是吗?

优秀后@ Eugene的回答:

every action below (after)

在这里,我明白你的意思 - 很明显:int tmp = i易变读( // [StoreLoad] -- this one int tmp = i; // volatile load // [LoadStore] // [LoadLoad] )没有被重新排序。

int tmp = i

在这里,你又增加了一个障碍。它确保我们不会使用volatile load重新排序任何操作。但是,为什么它很重要?为什么我有疑虑?据我所知render() { return ( <View style={styles.viewStyle}> <Text style={styles.titleText}>{this.props.Nickname}</Text> <AutoTypingText text={"\nAge: " + this.props.Age + "\n" + "City: " + this.props.City + "\n" + "Recent Login: " + this.props.RecentActivity + "\n" + "Points: " + this.props.Points + "\n" + "About: \"" + this.props.About + "\"\n" } charMovingTime={0} style={textStyle} delay={0} onComplete={() => { console.log('done'); console.log(this.state) }} /> <View style={{ flexDirection: 'row', justifyContent: 'space-around' }}> <Button style={styles.buttonStyle} textStyle={styles.buttonText}> Exploit ! </Button> <Button style={styles.buttonStyle} textStyle={styles.buttonText}> Spare </Button> </View> </View> ); } } 保证:

在易失性负载可见之前,> 易失性负载后的每个操作都不会被重新排序。

我看到你写道:

  

需要有顺序一致性

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

1 个答案:

答案 0 :(得分:4)

有些事情,首先will be flushed to memory - 这是非常错误的。它几乎从不与主存储器冲洗 - 它通常会将StoreBuffer耗尽到L1并且它可以通过缓存一致性协议来同步所有缓存之间的数据,但是

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

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

请注意LoadStoreLoadLoad这两个障碍;用简单的英语表示Load之后的任何Storevolatile load/read都无法向前移动。屏障,他们不能重新订购&#34;以上&#34;挥发性负荷。

以下是volatile store的示例。

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

这意味着任何LoadStore都无法进入&#34;低于&#34;加载存储本身。

这基本上构建了之前发生的关系,volatile load获取负载volatile store发布商店(这也必须如何实现StoreLoad cpu缓冲区,但它几乎超出了问题的范围。)

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

不是吗,还有更多。需要顺序一致性,这就是为什么任何理智的实现都会保证挥发性本身不会被重新排序,因此会插入两个以上的围栏:

 // 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个是免费的 - 因为它是strong memory model。唯一需要实施的是StoreLoad。在其他CPU上,例如ARMlwsycn是使用的一条指令 - 但我不太了解它们。

通常 mfenceStoreLoadx86的一个不错的选择,但同样的事情可以通过lock add保证(AFAIK in a {更便宜的方式),这就是为什么你在那里看到它。基本上 StoreLoad障碍。是的 - 你的最后一句话是正确的,对于较弱的记忆模型 - 需要StoreStore障碍。在侧面说明,即通过构造函数中的final字段安全地发布引用时使用的内容。退出构造函数后,会插入两个栅栏:LoadStoreStoreStore

把这一切都拿出来 - 只要它没有违反任何规则,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]

基本上没有障碍阻止volatile storevolatile load一起重新排序(即:易失性负载将首先执行)明显引起问题;因此违反了顺序一致性。

你有点想通过Every action after volatile load won't be reordered before volatile load is visible来点btw(如果我没记错的话)。 使用volatile本身无法重新排序 - 其他操作可以自由重新排序。让我举个例子:

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

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

最后两项操作x = 3y = 4完全免费重新订购,它们无法浮动在易失性之上,但它们可以重新排序 - 通过自己订购。上面的例子是完全合法的:

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

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