资讯详情

指令重排

三菱fx2nplc详细说明功能指令应用

119元

包邮

(需用券)

去购买 >

9428994b8cb2753180a4b1a302c45709.png

指令重排

说到指令重排,让我们先了解一下Java内存模型(JMM)。

JMM围绕多线程的原子性、可见性和有序性,建立了关键技术点。

原子性(Atomicity)

原子性是指一个操作是不可中断的,即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其它线程干扰。

可见性(Visibility)

可见性是指当一个线程修改了一个共享变量值时,其他线程能否立即知道该修改。

串行程序没有可见性问题,因为某个变量在任何操作步骤中都被修改,所以在后续步骤中,读取该变量的值必须在修改后进行。

然而,在并行程序中,如果一个线程修改了一个整体变量,其他线程可能无法立即知道这一变量(这涉及编译器优化重排和硬件优化)

有序性(Ordering)

有序性是指在单线程环境中, 程序按顺序执行。.

在多线程环境中, 由于指令重排,程序执行可能会出现混乱。

` class OrderExample {

int a = 0;

boolean flag = false;

public void writer() {

// 在指令重排等场景下,以下两个执行顺序可能会发生变化

a = 1;

flag = true;

}

public void reader() {

if (flag) {

int i = a 1;

……

}

}

}`

假设线程A首先执行write()方法,然后线程B执行reader()方法,如果发生指令重排,线程B正在执行 int i = a 1;可能看不出a已经被赋值为1了。

指令重排

指令重排是指在程序执行过程中, 考虑性能, 编译器和CPU可能会重新排序指令

重新安排指令可以保证串行语义一致(否则我们的应用根本无法正常工作),但没有义务保证多线程之间的语义一致。

为什么需要重排指令?

之所以这样做,完全是因为性能考虑。首先,执行一个指令需要分为许多步骤。可分为以下步骤:

1.取指令阶段 IF (使用PC寄存器组和存储器)

取指令(Instruction Fetch,IF)阶段是从主存中获取指令寄存器的过程。

程序计数器PC该值用于指示当前指令在主存中的位置。当指令被取出时,PC根据指令字根据指令字的长度自动增加:如果是单字长指令,则(PC) 1àPC;若为双字长指令,则(PC) 2àPC,依此类推。

//PC -> AR -> Memory

//Memory -> IR

2.指示译码阶段 ID (指令寄存器组)

取出指令后,计算机立即进入指令翻译(Instruction Decode,ID)阶段。

在指令翻译阶段,指令翻译器根据预定的指令格式拆分和解释回收的指令,识别不同的指令类别和获取操作数的各种方法。

在组合逻辑控制的计算机中,指令译码器对不同的指令操作码产生不同的控制电位,形成不同的微操作序列;在微程序控制的计算机中,指令译码器使用指令操作码找到执行指令的微程序的入口,并从此执行。

// { 1.Ad

//Memory -> IR -> ID -> { 2.PC变化

// { 3.CU(Control Unit)

3.执行指令阶段 EX (ALU算术逻辑单元)

取指令和指令翻译阶段后,进入执行指令(Execute,EX)阶段。

本阶段的任务是完成指令规定的各种操作,具体实现指令的功能。CPU的不同部分被连接起来,以执行所需的操作。

例如,算术逻辑单元需要完成加法操作ALU输入端一组输入和一组输出,输入端提供需要添加的值,输出端将包含最终的计算结果。

//Memory -> DR -> ALU

4.访问存取数阶段 MEM

根据指令,有可能访问主存,读取操作数,从而进入访问存取数(Memory,MEM)阶段。

本阶段的任务是根据指令地址码获取主存中操作数的地址,并从主存中读取操作数进行操作。

//Ad -> AR -> AD -> Memory

5.结果写回阶段 WB (寄存组)

结果作为最后一阶段写回来(Writeback,WB)将执行指令阶段的运行结果数据写回到某种存储形式:结果数据经常被写回CPU在内部寄存器中,以便快速访问后续指令;在某些情况下,结果数据也可以写入相对缓慢但便宜且容量大的主存。许多指令还会改变程序状态字寄存器中标记位的状态,标记不同的操作结果,可以用来影响程序的动作。

//DR -> Memory

6.循环阶段

在指令执行和结果数据写回后,如果没有事故(如结果溢出等),计算机将继续从程序计数器PC中取得下一条指令地址,开始新一轮的循环,下一个指令周期将顺序取出下一条指令。

//重复 1~5

//遇hlt(holt on)停止

由于每一步都可以用不同的硬件完成,每次只执行一个指令, 依次执行效率过低(导致其他硬件中断),因此发明了流水线技术来执行指令。

装配线技术是将指令分解为多个步骤,并重叠不同指令的每个步骤,以实现几个指令的并行处理。

指令1 IF ID EX MEN WB

指令2 IF ID EX MEN WB

指令的每一步都是由不同的硬件完成的,假设每一步需要1次时间ms,执行一个指令需要5个时间ms,每个指令都按顺序执行,那两个指令需要10ms。

但指令1刚刚通过流水线实施IF,执行IF指令2的硬件立即开始执行IF,这样,指令2只需等待1ms,执行两个指令只需要6个ms,效率会大大提高!

因此,流水线技术可以使用CPU高效执行,当装配线满载时,所有硬件都有序高效执行,但一旦中断,所有硬件设备将进入停止期,再次满载

需要几个周期,所以性能损失会比较大,所以一定要想办法尽量不要中断流水线!

此时,反映了指令重排的重要性。当然,指令重排只是一种减少中断的技术。事实上,在CPU更多的软硬件技术将用于防止中断。

现在来看看代码 A=B C 如何执行

现有R1,R2,R三三个寄存器,

LW R1,B IF ID EX MEN WB(加载B到R1中)

LW R2,C IF ID EX MEN WB(加载C到R2中)

ADD R3,R2,R1 IF ID × EX MEN WB(R1,R2相加放到R3)

SW A,R3 IF ID x EX MEN WB(把R3 将值保存到变量A)

在ADD执行指令中有一个x,表示中断、停顿,ADD为什么要在这里停顿?因为C还没有加载到这个时候R在2中,只能等待,而这种等待使后面的所有指令都停止了。

这种停顿可以避免吗?当然,可以通过重新安排指令来实现。让我们看看下面的例子:

执行A=B C;D=E-F;

通过将D=E-F提前执行指令顺序,以消除等待加载的时间。

1、LW Rb,B IF ID EX MEN WB

2、LW Rc,C IF ID EX MEN WB

3、LW Re,E IF ID EX MEN WB

4、ADD Ra,Rb,Rc IF ID EX MEN WB

5、LW Rf,F IF ID EX MEN WB

6、SW A,Ra IF ID EX MEN WB

7、SUB Rd,Re,Rf IF ID EX MEN WB

8、SW D,Rd IF ID EX MEN WB

在CPU硬件中断停顿等待时, 可以加载其他数据,更有效地利用资源,节省时间。如果不指令重新排列,则白白等待,效率较低。

编译器优化

主要指jvm层面的, 如下代码, 在jvm client模式很快就跳出来了while循环, 而在server在模式下运行, 永不停止

`/**

Created by Administrator on 2020/11/19

*/

public class VisibilityTest extends Thread {

private boolean stop;

public void run() {

int i = 0;

whie (!stop) {

i++;

}

System.out.println("finish loop,i=" + i);

}

public void stopIt() {

stop = true;

}

public boolean getStop() {

return stop;

}

public static void main(String[] args) throws Exception {

VisibilityTest v = new VisibilityTest();

v.start();

Thread.sleep(1000);

v.stopIt();

Thread.sleep(2000);

System.out.println("finish main");

System.out.println(v.getStop());

}

}`

两者区别在于当jvm运行在-client模式的时候,使用的是一个代号为C1的轻量级编译器,而-server模式启动的虚拟机采用相对重量级,代号为C2的编译器. C2比C1编译器编译的相对彻底,会导致程序启动慢, 但服务起来之后, 性能更高, 同时有可能带来可见性问题.

再来看两个从Java语言规范中摘取的例子, 也是涉及到编译器优化重排, 这里不再做详细解释,可查询相关文档

例子1中有可能出现r2 = 2 并且 r1 = 1;

例子2中是r2, r5值因为都是=r1.x, 编译器会使用向前替换, 把r5指向到r2, 最终可能导致r2=r5=0, r4 = 3;

禁止乱序

CPU层面:

在Intel架构中。利用原语指令(SFENCE,LFENCE,MFENCE) 或者总线方式。

sfence指令为写屏障(Store Barrier),作用是:

保证了sfence前后Store指令的顺序,防止Store重排序

通过刷新Store Buffer保证sfence之前的Store要指令对全局可见

lfence指令读屏障(Load Barrier),作用是:

保证了lfence前后的Load指令的顺序,防止Load重排序

刷新Load Buffer

mfence指令全屏障(Full Barrier),作用是:

保证了mfence前后的Store和Load指令的顺序,防止Store和Load重排序

保证了mfence之后的Store指令全局可见之前,mfence之前的Store指令要先全局可见

JVM层级:8个hanppens-before原则 4个内存屏障 (LL LS SL SS)

Happen-Before先行发生规则

如果光靠sychronized和volatile来保证程序执行过程中的原子性, 有序性, 可见性, 那么代码将会变得异常繁琐.

JMM提供了8个Happen-Before规则来约束数据之间是否存在竞争, 线程环境是否安全, 具体如下:

顺序原则:一个线程内保证语义的串行性; a = 1; b = a + 1;

volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性,

锁规则:解锁(unlock)必然发生在随后的加锁(lock)前.

传递性:A先于B,B先于C,那么A必然先于C.

线程的start()方法先于它的每一个动作.

线程的所有操作先于线程的终结(Thread.join()).

线程的中断(interrupt())先于被中断线程的代码.

对象的构造函数执行结束先于finalize()方法.

4个内存屏障 (LL LS SL SS)

LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。

StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。

LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。

StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。

as-if-serial

As-if-serial语义的意思是,所有的动作(Action)都可以为了优化而被重排序,但是必须保证它们重排序后的结果和程序代码本身的应有结果是一致的。Java编译器、运行时和处理器都会保证单线程下的as-if-serial语义。

java 11官方入门(第8版)教材

79.84元

包邮

(需用券)

去购买 >

标签: 数字电位器ad8400ar10

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台