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