pipeline:

现在的CPU一般采用流水线方式来执行指令。一个指令执行周期被分成:取值,译码,执行,访存,写会,更新PC若干阶段。然后,多条指令可以同时存在于流水线中,同时被执行,来提高系统的吞吐量。

流水线并不是串行的,并不会因为一个耗时很长的执行在"执行"阶段呆很长时间,而导致后续的指令被卡在"执行"阶段之前上。相反,流水线是并行的,多条指令可以同时处于同一阶段,只要CPU内部的处理部件未被占满既可。比如说CPU有一个加法器和一个除法器,那么一条加法指令和一条除法指令就可能同时处于"执行"阶段,而两条加法指令在"执行"阶段就只能被串行工作。

然而,这样一来,乱序就可能产生了。比如一条加法指令本来出现在一条除法指令的后面,但是由于除法的执行时间很长,在他执行完之前,加法可能先执行完了,再比如两条访存指令,可能由于第二胎哦指令命中cache而导致他先于第一条指令完成。

一般情况下,指令乱序并不是CPU在执行指令之前刻意去调整顺序。CPU总是顺序的去内存里面取指令,然后将其顺序的方法指令流水线。但是指令执行时的各种条件,指令与指令之间的相互影响,可能导致顺序放入流水线的指令,最终乱序执行完成,这就是所谓的"顺序流入,乱序流出"。

指令流水线除了在资源不足的情况下会卡主之外(如前所述的一个加法器应付两条加法指令的情况),指令之间的相关性也是导致流水线阻塞的重要原因。

CPU的乱序执行并不是任意的乱序,而是以保障程序上下文因果关系为前提的。有了这个前提,CPU执行的正确性才有有保证:

a++; b = f(a); c--;

由于b = f(a)这条指令依赖于前一条指令a++的执行结果,所以b = f(a)将在"执行"阶段之前被阻塞,知道a++的执行结果被生成出来;而c--跟前面没有依赖,他可以在b = f(a)之前就能执行完。像这样有依赖关系的指令如果挨着很近,后一条指令必定会因为等待前一条执行的结果,而在流水线中阻塞很久,占用流水线的资源。

而编译器的乱序,作为编译优化的一种手段,则试图通过指令重排序将这样的两条指令拉开距离,以至于后一条指令进入CPU的时候,前一条指令结果已经可以得到了,那么也就不需要阻塞等待了,比如指令重拍为:

a++ ; c-- ; b = f(a);

相对于CPU的乱序,编译器的乱序才是真正的对指令顺序做了调整。但是编译器的乱序也必须保证程序上下文的因果关系不发生改变。

乱序的后果:

乱序执行,有了"保证上下文英国关系"这一前提,一般情况下不会有什么问题的,因此,在绝大多数情况下,我们写程序都不去考虑乱序所带来的影响。但是,有些程序逻辑,单纯从上下文是看不出他们的因果关系的。比如:

*addr = 5 ; val = *data;

从表面上看,addr和data是没有什么联系的,完全可以放心的去乱序执行,但是如果这是在xx设备驱动程序中,这两个变量可可能对应到设备的地址端口和数据端口。并且,这个设备规定了,当你需要读写设备上某个寄存器时,先将寄存器编号设置到地址端口,然后就可以通过对数据端口的读写而操作对应的寄存器,那么这么一来,对前面那两条指令的乱序执行就可能造成错误。对于这样的逻辑,我们姑且将其称作隐式的因果关系;而指令与指令之间直接的输入输出依赖,称之为显式的因果关系。CPU或者编译器的乱序是以保证显式的因果关系不变为前提的,但是他们都无法识别隐式的因果关系。再举个例子:

object -> data = xxx;  object -> ready = true;

当设置了data之后,记下标志,然后在另一个线程中可能执行:

if (object -> ready) do_something(object -> data);

如果考虑到乱序,如果标志被赋值先于data被赋值, 那么结果就可能杯具了,因为从字面上看,前面的那两条指令其实并不存在显式的因果关系,乱序是有可能发生的。

总的来说,如果程序有显式的因果关系的话,乱序一定会尊重这些关系;否则,乱序就可能打破程序原有的逻辑。这时候,就需要使用屏障来抑制乱序,以维持程序所期望的逻辑。

Memory barrier:

内存屏障主要有:读屏障,写屏障,通用屏障,优化屏障;

以读屏障为例,他用于保证读操作有序。屏障之前的读操作一定会先于屏障之后的读操作完成,写操作不受影响,同属于屏障的某一侧的读操作中也不受影响。类似的,写屏障用于限制写操作。而通用屏障则对读写操作都有作用。而优化屏障则用于限制编译器的指令重排,不区分读写。前三种屏障都隐含了优化屏障的功能,比如:

tmp = ttt ;  *addr = 5 ; memoryBarrier(); var = *data;

有了内存屏障就可以确保先设置地址端口,再读取数据端口。而至于设置地址读卡和tmp的赋值孰先孰后,屏障则不做干预。

有了内存屏障,就可以在隐式的因果关系的场景中,保证因果关系逻辑正确。

多处理器情况:

      前面只是考虑了单处理器指令乱序的问题,而在多处理器下,除了每个处理器要独自面对上面讨论的问题之外,当处理器之前存在交互的时候,同样要面对乱序的问题。

一个处理器(记为a)对内存的写操作证并不是直接就在内存上生效的,而是要先经过自身的cache。另一个处理器(记为b)如果要独缺相应内存上的新值,先得等a的cache同步到内存,然后b的cache再从内存同步这个新值。而如果需要同步的值不止一个的话,就会存在顺序问题。再举前面的一个例子:

 <cpu - a>   *************************************** <cpu - b>
object -> data = xxx;
write-memory-barrier(); if (object -> ready)
object -> ready = true; do_something(object -> data);

前面也说过,必须要使用屏障来保证CPU-a不发生乱序,从而使得ready标记赋值时候,data一定是有效的。但是在多处理器情况下,这还不够。data和ready标记的新值可能以相反的顺序更新到CPU-b上!

其实这种情况在大多数体系结构下并不会发生,不过内核文档memory-barriers举了一个alpha机器的例子。alpha机器可能使用分列的cache结构,每个cache列可以并行工作,以提高效率。而每个cache列上面的缓存的数据是互斥的(如果不互斥就还得解决cache列之间的一致性),于是就可能引发cache更新不同步的问题。

假设cahce被分为两列,而CPU-a和CPU-b上的data和ready都分别被缓存到不同的cache列中。首先是CPU-a更新了cache之后,会发送消息让其他CPU的cache来同步新的值。但是现在假设了有两个cache列,可能由于缓存data的cache列比较繁忙而使得data的更新消息晚于ready发出,那么程序逻辑就没法保证了。不过在SMP下的内存屏障在解决指令乱序的问题在外,也将cache更新消息乱序的问题解决了。只要使用了屏障,就能保证屏障之前的cache更新消息先于屏障支护的消息被发出。

然后就是CPU-b的问题。在使用了屏障之后,CPU-a已经保证data的更新消息先发出了,那么CPU-b也会先收到data的更新消息。不过同样,CPU-b上缓存data的cahce列可能比较繁忙,导致对data的更新晚于对ready的更新,这里同样会出问题。

所以,在这种情况下,CPU-b也得使用屏障,CPU-a使用写屏障,保证两个写操作不乱序,并且相应的两个cache列的更新消息不乱序;CPU-b上则需要使用读屏障,保证对两个cache单元的同步不乱序,可见,SMP下的内存屏障一定是需要配对使用的。

 <cpu - a> ************************************************* <cpu - b>
object -> data = xxx; if (object -> ready)
write-memory-barrier(); read-memory-barrier();
object -> ready = true; do_something(object -> data);

原文:http://blog.csdn.net/jiang_bing/article/details/8629425

J.U.C JMM. pipeline.指令重排序,happen-before的更多相关文章

  1. J.U.C JMM. pipeline.指令重排序,happen-before(续)

    前面已经介绍硬件平台Cache Coherence问题和解决办法,下面来看看Java虚拟机平台的相关知识.硬件平台处理器,高速缓存,主存之间的交互关系如下: Java内存模型(JMM)         ...

  2. J.U.C JMM. pipeline.指令重排序,happen-before(续MESI协议)

    缓存(Cache)       CPU的读/写(以及取指令)单元正常情况下甚至都不能直接访问内存——这是物理结构决定的:CPU都没有管脚直接连到内存.相反,CPU和一级缓存(L1 Cache)通讯,而 ...

  3. 使用 volatile 关键字保证变量可见性和禁止指令重排序

    volatile 概述 volatile 是 Java 提供的一种轻量级的同步机制.相比于传统的 synchronize,虽然 volatile 能实现的同步性要差一些,但开销更低,因为它不会引起频繁 ...

  4. Java的多线程机制系列:不得不提的volatile及指令重排序(happen-before)

    一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...

  5. Java的多线程机制系列:(四)不得不提的volatile及指令重排序(happen-before)

    一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...

  6. 指令重排序及Happens-before法则随笔

    指令重排序 对主存的一次访问一般花费硬件的数百次时钟周期.处理器通过缓存(caching)能够从数量级上降低内存延迟的成本这些缓存为了性能重新排列待定内存操作的顺序.也就是说,程序的读写操作不一定会按 ...

  7. 深入浅出Java并发包—指令重排序

    前面大致提到了JDK中的一些个原子类,也提到原子类是并发的基础,更提到所谓的线程安全,其实这些类或者并发包中的这么一些类,都是为了保证系统在运行时是线程安全的,那到底怎么样才算是线程安全呢? Java ...

  8. 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则

    转: http://www.blogjava.net/xylz/archive/2010/07/03/325168.html 在这个小结里面重点讨论原子操作的原理和设计思想. 由于在下一个章节中会谈到 ...

  9. 轻松学JVM(二)——内存模型、可见性、指令重排序

    上一篇我们介绍了JVM的基本运行流程以及内存结构,对JVM有了初步的认识,这篇文章我们将根据JVM的内存模型探索java当中变量的可见性以及不同的java指令在并发时可能发生的指令重排序的情况. 内存 ...

随机推荐

  1. Node.js 蚕食计划(四)—— Express + SQL Server 搭建电影网站

    前段时间在慕课网上看了 scott 大神的<node+mongodb建站攻略>课程,按照自己的思路做了一遍,发博客记录一下 一.项目介绍 这个项目是一个简单的电影网站,由首页.详情页.评论 ...

  2. [Python学习] Django 权限控制

    本文为大家讲解 Django 框架里自带的权限模型,从理论到实战演练,带领大家了解 Django 里权限是怎么一回事. 一.主要内容 1.什么是权限管理? 2.Web 权限 3.Django 权限机制 ...

  3. Python中的单例模式的几种实现方式的优缺点及优化

    单例模式 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在.当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场. ...

  4. (笔记):组合and继承之访问限制(二)

    上篇简单介绍了public与private的基本使用.private的访问限制相对复杂.针对这种访问属性,我们会想到有没有一种方式可以无视这种属性.答案是:有.我们可以通过friend的方式(可以破解 ...

  5. Java学习笔记5(类的入门以及ArrayList)

    1.类的概念:将现实生活中的事物抽象成了代码(类),我们可以使用自定义的数组类型(类)来描述现实生活中的事物. 2.分析:用一部手机来分析,手机可以打电话,上网,听音乐,这些就是方法,手机有型号,颜色 ...

  6. Hadoop 2.6.0 完全分布式平台搭建

    一.准备软件环境: hadoop-2.6.0.tar.gz CentOS release 6.5 jdk-7u67-linux-x64.tar.gz 网络配置: master1            ...

  7. Centos7下配置Python3和Python2共存,以及对应版本Ipython安装配置

    1.查看是否已经安装Python Centos7默认安装了python2.7.5 因为一些命令要用它比如yum 它使用的是python2.7.5. 使用python -V命令查看一下是否安装Pytho ...

  8. three.js 相机

    图形学中的相机定义了三维空间到二维屏幕的投影方式,根据投影方式的不同,相机可分为 正交投影相机 与 透视投影相机. 正交投影相机 : 近处.远处的物体大小尺寸保持一致,常适用于工程制图.建模软件,如C ...

  9. .net的retrofit--WebApiClient库深入篇

    前言 本篇文章的内容是对上一篇.net的retrofit--WebApiClient库的深层次补充,你可能需要先阅读上一篇才能理解此篇文章.本文将详细地讲解WebApiClient的原理,结合实际项目 ...

  10. 从交叉熵损失到Facal Loss

    1交叉熵损失函数的由来1.1关于熵,交叉熵,相对熵(KL散度) 熵:香农信息量的期望.变量的不确定性越大,熵也就越大,把它搞清楚所需要的信息量也就越大.其计算公式如下: 其是一个期望的计算,也是记录随 ...