指令重排

我们知道java在运行的时候有两个地方可能用到重排序,一个是编译器编译的的时候,一个是处理器运行的时候。

那么我们就应该问问为啥要用指令重排序呢?

编译期重排序有啥好处?

CPU计算的时候要访问值,如果常常利用到寄存器中已有的值就不用去内存读取了,比如说

int a = 1;
int b = 1;
a = a + 1;
b = b +1 ;

就可能没有

int a = 1;
a = a + 1;
int b = 1;
b = b +1 ;

性能好,因为后者的a或b可能在寄存器中了。

处理器为啥要重排序?

一条汇编指令的执行步骤:

  • 取指 IF

  • 译码和取寄存器操作 ID

  • 执行或者有效地址计算 EX

  • 存储器访问 MEM

  • 写回 WB

在CPU工作中汇编指令分多步完成,每一部涉及到的硬件(寄存器)可能不同,于是有了流水线技术来执行指令。

没有流水线技术前,如果同时两个指令过来执行 一个需要5秒,那么两个就需要10秒;有了流水线技术之后,可能就只要6秒。多个指令同时执行时性能显著提升。

流水线技术是一种将指令分解为多步,并让不同指令的各步操作重叠,从而实现几条指令并行处理。

指令1 IF ID EX MEN WB

指令2 IF ID EX MEN WB

指令的每一步都由不同的硬件完成,假设每一步耗时1ms,执行完一条指令需耗时5ms,

每条指令都按顺序执行,那两条指令则需10ms。

但是通过流水线在指令1刚执行完IF,执行IF的硬件立马就开始执行指令2的IF,这样指令2只需要等1ms,两个指令执行完只需要6ms,效率是不是提升巨大!

这个和指令重排有啥关系?

流水线技术并不是说多个汇编指令都能并行执行,还是需要等他其他指令执行完才可以执行(比如ADD指令需要等待LW指令读取寄存器数据完成),那么在这个等待过程中,我们可以让和这个指令不相干的后面的指令先执行(比如另外一个表达式的LW指令),这就是指令重排。

先记住几个指令:

LW(加载数据到寄存器的指令)

ADD(两个定点寄存器的内容相加)

SUB(相减)

SW(把数据从寄存器存储到存储器)

现在来看一下代码 A=B+C 是怎么执行的

这个停顿可以避免吗?

当然是可以的,通过指令重排就可以实现,再看一下下面的例子:

要执行

A=B+C;

D=E-F;

与其让第一个表达式的ADD指令阻塞等待,还不如让第二个表达式的LW指令先执行,注意两个表达式的LW指令操作的是不同的寄存器,所以可以并行执行。

我们写一段代码来试试:

运行结果中:

也就是先用mov把方法里面所需要的三个value加载了,再统一用add进行加法运算。

现在我们把//***哪一行注释掉,运行结果如下:

依然是先把所有value都用mov指令加载后再进行加法运算。 总结起来就是不管代码里这个值使用顺序多靠后,都先用mov加载后再使用add对这个值进行运算。

注意,上面的运行参数为-Xcomp -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*ReOrder.add1 -XX:+PrintCompilationXcomp 含义是使用编译模式而不是解释模式, -XX:CompileCommand=print,*ReOrder.add1表示只打印这个方法,-XX:+PrintCompilation表示打印方法名称。 需要插件hsdis,编译好后放在jdk的jre的bin的server中就好,具体环境搭建可以参阅这里

参考

汇编语言入门教程

为什么要指令重排序?

编译器为什么要做指令重排呢

计算机指令执行过程详解

Java内存访问重排序的研究

最后更新于

这有帮助吗?