指令重排

我们知道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 是怎么执行的

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

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还没加载到R2中,只能等待,而这个等待使得后边的所有指令都会停顿一下。

这个停顿可以避免吗?

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

要执行

A=B+C;

D=E-F;

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

LW Rb,B    IF  ID  EX MEN WB

LW Rc,C       IF  ID  EX MEN   WB

LW Re,E          IF  ID  EX MEN   WB

ADD Ra,Rb,Rc          IF  ID  EX MEN   WB

LW Rf,F                  IF  ID  EX MEN    WB

SW A,Ra                     IF  ID  EX MEN    WB

SUB Rd,Re,Rf                    IF  ID  EX    MEN   WB

SW D,Rd                            IF  ID    EX MEN   WB

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

我们写一段代码来试试:

package *****;

/**
 * reorder
 * @author Mageek Chiu
 * @date 2018/5/25 0025:12:49
 */
public class ReOrder {

    public int value ;

    private ReOrder(int value) {
        this.value = value;
    }

    public static void main(String... args){
        ReOrder reOrder = new ReOrder(111);
        ReOrder reOrder1 = new ReOrder(222);
        ReOrder reOrder2 = new ReOrder(333);
        System.out.println(add1(reOrder,reOrder1,reOrder2));
    }

    static int add1(ReOrder reOrder,ReOrder reOrder1,ReOrder reOrder2){
        int result = 0;

        result += reOrder.value;
        result += reOrder1.value;
        result += reOrder2.value;//***

        result += reOrder.value;
        result += reOrder1.value;
        result += reOrder2.value;

        result += reOrder.value;
        result += reOrder1.value;
        result += reOrder2.value;

        return result;

    }

}

运行结果中:

 # {method} {0x000000001c402c80} 'add1' '(*****/ReOrder;*****/ReOrder;*****/ReOrder;)I' in '*****/ReOrder'
  # parm0:    rdx:rdx   = '*****/ReOrder'
  # parm1:    r8:r8     = '*****/ReOrder'
  # parm2:    r9:r9     = '*****/ReOrder'
  #           [sp+0x20]  (sp of caller)
  0x00000000032a86c0: mov     dword ptr [rsp+0ffffffffffffa000h],eax
  0x00000000032a86c7: push    rbp
  0x00000000032a86c8: sub     rsp,10h           ;*synchronization entry
                                                ; - *****.ReOrder::add1@-1 (line 24)

  0x00000000032a86cc: mov     r11d,dword ptr [rdx+0ch]
                                                ;*getfield value
                                                ; - *****.ReOrder::add1@4 (line 26)
                                                ; implicit exception: dispatches to 0x00000000032a86ff
  0x00000000032a86d0: mov     r10d,dword ptr [r8+0ch]  ;*getfield value
                                                ; - *****.ReOrder::add1@11 (line 27)
                                                ; implicit exception: dispatches to 0x00000000032a870d
  0x00000000032a86d4: mov     r9d,dword ptr [r9+0ch]  ;*getfield value
                                                ; - *****.ReOrder::add1@18 (line 28)
                                                ; implicit exception: dispatches to 0x00000000032a8719
  0x00000000032a86d8: mov     eax,r11d
  0x00000000032a86db: add     eax,r10d
  0x00000000032a86de: add     eax,r9d
  0x00000000032a86e1: add     eax,r11d
  0x00000000032a86e4: add     eax,r10d
  0x00000000032a86e7: add     eax,r9d
  0x00000000032a86ea: add     eax,r11d
  0x00000000032a86ed: add     eax,r10d
  0x00000000032a86f0: add     eax,r9d           ;*iadd

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

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

[Constants]
  # {method} {0x000000001c052c78} 'add1' '(*****/ReOrder;*****/ReOrder;*****/ReOrder;)I' in '*****/ReOrder'
  # parm0:    rdx:rdx   = '*****/ReOrder'
  # parm1:    r8:r8     = '*****/ReOrder'
  # parm2:    r9:r9     = '*****/ReOrder'
  #           [sp+0x20]  (sp of caller)
  0x0000000002f47d40: mov     dword ptr [rsp+0ffffffffffffa000h],eax
  0x0000000002f47d47: push    rbp
  0x0000000002f47d48: sub     rsp,10h           ;*synchronization entry
                                                ; - *****.ReOrder::add1@-1 (line 24)

  0x0000000002f47d4c: mov     r11d,dword ptr [rdx+0ch]
                                                ;*getfield value
                                                ; - *****r.ReOrder::add1@4 (line 26)
                                                ; implicit exception: dispatches to 0x0000000002f47d7c
  0x0000000002f47d50: mov     r10d,dword ptr [r8+0ch]  ;*getfield value
                                                ; - *****.ReOrder::add1@11 (line 27)
                                                ; implicit exception: dispatches to 0x0000000002f47d89
  0x0000000002f47d54: mov     r9d,dword ptr [r9+0ch]  ;*getfield value
                                                ; - *****::add1@32 (line 32)
                                                ; implicit exception: dispatches to 0x0000000002f47d95
  0x0000000002f47d58: mov     eax,r11d
  0x0000000002f47d5b: add     eax,r10d
  0x0000000002f47d5e: add     eax,r11d
  0x0000000002f47d61: add     eax,r10d
  0x0000000002f47d64: add     eax,r9d
  0x0000000002f47d67: add     eax,r11d
  0x0000000002f47d6a: add     eax,r10d
  0x0000000002f47d6d: add     eax,r9d           ;*iadd

依然是先把所有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内存访问重排序的研究

版权

评论