我们知道java在运行的时候有两个地方可能用到重排序,一个是编译器编译的的时候,一个是处理器运行的时候。
那么我们就应该问问为啥要用指令重排序呢?
生活类比
我们从生活中举个例子,假设你有一箱红纸,现在要你剪成小红花贴在窗上。你有两种极端的选择:拿出来一个,把这个剪好,再贴上去......一个一个依次进行;另一种方式是先全部拿出来,然后全部剪好,最后全部贴上去。
那种效率更高?很明显是后者,因为前者你就需要不停地在箱子,剪刀和胶水之间切换,这个切换过程不仅浪费时间,还耗费精力。但是后者一直做一个工作也很无聊,还会导致半天了窗上一朵花都没有,会给你带来失落感,所以比较合适的做法就是拿出来一叠,把这一叠剪好,贴上去。这样既不无聊,也减少了切换次数,提高了工作效率。
再想想,如果有三个人,一个负责拿,一个负责剪,一个负责贴,就更快了。
分析
编译期重排序有啥好处?CPU计算的时候要访问值,如果常常利用到寄存器中已有的值就不用去内存读取了,比如说
代码语言:txt复制int a = 1;
代码语言:txt复制int b = 1;
代码语言:txt复制a = a 1;
代码语言:txt复制b = b 1 ;
就可能没有
代码语言:txt复制int a = 1;
代码语言:txt复制a = a 1;
代码语言:txt复制int b = 1;
代码语言:txt复制b = b 1 ;
性能好,因为后者可以 a或b可能在寄存器中了。
处理器为啥要重排序?因为一个汇编指令也会涉及到很多步骤,每个步骤可能会用到不同的寄存器,CPU使用了流水线技术,也就是说,CPU有多个功能单元(如获取、解码、运算和结果),一条指令也分为多个单元,那么第一条指令执行还没完毕,就可以执行第二条指令,前提是这两条指令功能单元相同或类似,所以一般可以通过指令重排使得具有相似功能单元的指令接连执行来减少流水线中断的情况。
我们写一段代码来试试:
代码语言:txt复制package *****;
代码语言:txt复制/**
代码语言:txt复制 * reorder
* @author Mageek Chiu
* @date 2018/5/25 0025:12:49
*/
public class ReOrder {
代码语言:txt复制 public int value ;
代码语言:txt复制 private ReOrder(int value) {
代码语言:txt复制 this.value = value;
代码语言:txt复制 }
代码语言:txt复制 public static void main(String... args){
代码语言:txt复制 ReOrder reOrder = new ReOrder(111);
代码语言:txt复制 ReOrder reOrder1 = new ReOrder(222);
代码语言:txt复制 ReOrder reOrder2 = new ReOrder(333);
代码语言:txt复制 System.out.println(add1(reOrder,reOrder1,reOrder2));
代码语言:txt复制 }
代码语言:txt复制 static int add1(ReOrder reOrder,ReOrder reOrder1,ReOrder reOrder2){
代码语言:txt复制 int result = 0;
代码语言:txt复制 result = reOrder.value;
代码语言:txt复制 result = reOrder1.value;
代码语言:txt复制 result = reOrder2.value;//***
代码语言:txt复制 result = reOrder.value;
代码语言:txt复制 result = reOrder1.value;
代码语言:txt复制 result = reOrder2.value;
代码语言:txt复制 result = reOrder.value;
代码语言:txt复制 result = reOrder1.value;
代码语言:txt复制 result = reOrder2.value;
代码语言:txt复制 return result;
代码语言:txt复制 }
代码语言:txt复制}
运行结果中:
代码语言:txt复制 # {method} {0x000000001c402c80} 'add1' '(*****/ReOrder;*****/ReOrder;*****/ReOrder;)I' in '*****/ReOrder'
代码语言:txt复制 # parm0: rdx:rdx = '*****/ReOrder'
代码语言:txt复制 # parm1: r8:r8 = '*****/ReOrder'
代码语言:txt复制 # parm2: r9:r9 = '*****/ReOrder'
代码语言:txt复制 # [sp 0x20] (sp of caller)
代码语言:txt复制 0x00000000032a86c0: mov dword ptr [rsp 0ffffffffffffa000h],eax
代码语言:txt复制 0x00000000032a86c7: push rbp
代码语言:txt复制 0x00000000032a86c8: sub rsp,10h ;*synchronization entry
代码语言:txt复制 ; - *****.ReOrder::add1@-1 (line 24)
代码语言:txt复制 0x00000000032a86cc: mov r11d,dword ptr [rdx 0ch]
代码语言:txt复制 ;*getfield value
代码语言:txt复制 ; - *****.ReOrder::add1@4 (line 26)
代码语言:txt复制 ; implicit exception: dispatches to 0x00000000032a86ff
代码语言:txt复制 0x00000000032a86d0: mov r10d,dword ptr [r8 0ch] ;*getfield value
代码语言:txt复制 ; - *****.ReOrder::add1@11 (line 27)
代码语言:txt复制 ; implicit exception: dispatches to 0x00000000032a870d
代码语言:txt复制 0x00000000032a86d4: mov r9d,dword ptr [r9 0ch] ;*getfield value
代码语言:txt复制 ; - *****.ReOrder::add1@18 (line 28)
代码语言:txt复制 ; implicit exception: dispatches to 0x00000000032a8719
代码语言:txt复制 0x00000000032a86d8: mov eax,r11d
代码语言:txt复制 0x00000000032a86db: add eax,r10d
代码语言:txt复制 0x00000000032a86de: add eax,r9d
代码语言:txt复制 0x00000000032a86e1: add eax,r11d
代码语言:txt复制 0x00000000032a86e4: add eax,r10d
代码语言:txt复制 0x00000000032a86e7: add eax,r9d
代码语言:txt复制 0x00000000032a86ea: add eax,r11d
代码语言:txt复制 0x00000000032a86ed: add eax,r10d
代码语言:txt复制 0x00000000032a86f0: add eax,r9d ;*iadd
也就是先用mov
把方法里面所需要的三个value
加载了,再统一用add进行加法运算。
现在我们把//***
哪一行注释掉,运行结果如下:
[Constants]
代码语言:txt复制 # {method} {0x000000001c052c78} 'add1' '(*****/ReOrder;*****/ReOrder;*****/ReOrder;)I' in '*****/ReOrder'
代码语言:txt复制 # parm0: rdx:rdx = '*****/ReOrder'
代码语言:txt复制 # parm1: r8:r8 = '*****/ReOrder'
代码语言:txt复制 # parm2: r9:r9 = '*****/ReOrder'
代码语言:txt复制 # [sp 0x20] (sp of caller)
代码语言:txt复制 0x0000000002f47d40: mov dword ptr [rsp 0ffffffffffffa000h],eax
代码语言:txt复制 0x0000000002f47d47: push rbp
代码语言:txt复制 0x0000000002f47d48: sub rsp,10h ;*synchronization entry
代码语言:txt复制 ; - *****.ReOrder::add1@-1 (line 24)
代码语言:txt复制 0x0000000002f47d4c: mov r11d,dword ptr [rdx 0ch]
代码语言:txt复制 ;*getfield value
代码语言:txt复制 ; - *****r.ReOrder::add1@4 (line 26)
代码语言:txt复制 ; implicit exception: dispatches to 0x0000000002f47d7c
代码语言:txt复制 0x0000000002f47d50: mov r10d,dword ptr [r8 0ch] ;*getfield value
代码语言:txt复制 ; - *****.ReOrder::add1@11 (line 27)
代码语言:txt复制 ; implicit exception: dispatches to 0x0000000002f47d89
代码语言:txt复制 0x0000000002f47d54: mov r9d,dword ptr [r9 0ch] ;*getfield value
代码语言:txt复制 ; - *****::add1@32 (line 32)
代码语言:txt复制 ; implicit exception: dispatches to 0x0000000002f47d95
代码语言:txt复制 0x0000000002f47d58: mov eax,r11d
代码语言:txt复制 0x0000000002f47d5b: add eax,r10d
代码语言:txt复制 0x0000000002f47d5e: add eax,r11d
代码语言:txt复制 0x0000000002f47d61: add eax,r10d
代码语言:txt复制 0x0000000002f47d64: add eax,r9d
代码语言:txt复制 0x0000000002f47d67: add eax,r11d
代码语言:txt复制 0x0000000002f47d6a: add eax,r10d
代码语言:txt复制 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
中就好,具体环境搭建可以参阅这里
分析不对的地方请轻拍。
undefined