为啥要指令重排序?

我们知道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可能在寄存器中了。

处理器为啥要重排序?

因为一个汇编指令也会涉及到很多步骤,每个步骤可能会用到不同的寄存器,现在的CPU一般采用流水线来执行指令,也就是说,CPU有多个功能单元(如获取、解码、运算和结果),一个指令的执行被分成:取指、译码、访存、执行、写回、等若干个阶段,流水线是并行的, 第一条指令执行还没完毕,就可以执行第二条指令,前提是这两条指令功能单元相同或类似,所以一般可以通过指令重排使得具有相似功能单元的指令接连执行来减少流水线中断的情况。

我们写一段代码来试试:

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:+PrintCompilation
Xcomp 含义是使用编译模式而不是解释模式, -XX:CompileCommand=print,*ReOrder.add1表示只打印这个方法,-XX:+PrintCompilation表示打印方法名称。
需要插件hsdis,编译好后放在jdk的jre的bin的server中就好,具体环境搭建可以参阅这里

 

参考下文:(作者:mageek)

http://mageek.cn/archives/99/

没有更多推荐了,返回首页