当前位置: 首页 > 面试题库 >

在volatile环境下分析JIT生成的x86输出

欧阳安阳
2023-03-14
问题内容

我写这篇文章与深入了解Java中的volatile有关

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原因,那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的答案后编辑:

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

在这里,我明白您的意思了-很明显:every action below (after)volatile读(int tmp = i)不会重新排序。

 // [StoreLoad] -- this one
 int tmp = i; // volatile load
 // [LoadStore]
 // [LoadLoad]

在这里,您又设置了一个障碍。它确保我们不会使用来重新排序任何动作int tmp = i。但是,为什么它很重要?为什么我有疑问?据我所知volatile load保证:

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

我看到你写:

必须有一个顺序的一致性

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


问题答案:

首先,有两件事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]

注意这里LoadStore和下面 的两个障碍LoadLoad。用简单的英语来说,这意味着任何LoadStore之后出现的volatile load/read 事物都不能“提升”障碍,它们不能被重新排序为“高于”波动性负载。

这是的示例volatile store

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

这意味着任何Load并且Store不能“低于”负载存储本身。

这基本上建立之前发生关系,volatile load是在 获取负荷volatile store作为 释放店
(这也与如何做StoreLoadCPU的缓冲区都实现,但是这几乎是不可能的范围)。

如果您考虑一下,那么对于我们volatile通常所了解的事情来说,这是完全有意义的;它说,一旦通过易失性负载 观察到 易失性存储, 也将观察到
a之前的所有内容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

现在,事实证明x864个内存屏障中有3个是空闲的-因为它是一个strong memory model。唯一需要实现的是StoreLoadARM例如,在其他CPU上lwsycn使用的是一条指令-但是我对它们并不了解。

通常 一个mfence是一个不错的选择StoreLoadx86,但同样的事情是通过保证lock add(以较便宜的方式AFAIK),这就是为什么你看到它在那里。基本上是 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 store对被重新排序与volatile load(即:易失性负载将被执行的 第一
),并且将明显导致问题; 因此违反了顺序一致性。

顺便说一句,如果您没记错的话,您有点错过了这一点Every action after volatile load won't be reordered before volatile load is visiblevolatile本身 无法进行重新排序-
其他操作可以自由进行重新排序。让我举一个例子:

 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


 类似资料:
  • 需要有一个连续的一致性 但是,我不明白为什么要有顺序的一致性。

  • 如果你使用了 vux2 模板或者 webpack 模板,默认你可以直接通过判断 process.env.NODE_ENV 来区分 比如统计代码仅放在 production 环境,在不同环境里使用不同的 API 接口地址。 if (process.env.NODE_ENV === 'production') { // 干一些线上才要做的事情 } if (process.env.NODE_ENV

  • 标签(空格分隔): EBOOKCHAIN PM2 NODEJS [TOC] 前言 部署前请先安装Ebookcoin 请参考官方wiki:https://github.com/Ebookcoin/ebookcoin/wiki/ pm2简介 Node.js默认单进程运行,对于32位系统最高可以使用512MB内存,对于64位最高可以使用1GB内存。对于多核CPU的计算机来说,这样做效率很低,因为只有一个

  • 我目前正在学习多线程,我发现了一些我无法解释的有趣的东西。据我所知,如果两个线程访问一个静态变量,它们可以将自己的副本复制到缓存中。Thread1对其本地缓存中的静态变量进行的更新不会反映在Thread2缓存的静态变量中。 java java

  • 目标:开发一个基于 Spring Boot 和 MongoDB 的应用,使用 MongoDB 记录访问者信息 本项目代码维护在 DaoCloud/docker-demo-java-mongo 项目中。 您可以在 GitHub 找到本项目并获取本文中所提到的所有代码文件。 前言 生产环境中的应用,少不了数据库和缓存的配合,在我们的这个教程中,将会给大家演示如何基于 Spring Boot 和 Mon