Java虚拟机JVM性能优化(二):编译器(4)
在字节码阶段,如果一个变量被加载但是永远不会被使用,编译器可以检测到并消除掉这些死代码,如列表2所示。如果永远不执行该加载操作则可以节约cpu时间从而改进程序的执行速度。
列表2. 优化后的代码
int timeToScaleMyApp(boolean endlessOfResources){
int reArchitect =24; //unnecessary operation removed here…
int useZing =2;
if(endlessOfResources)
return reArchitect + useZing;
else
return useZing;
}
冗余消除是一种类似移除重复指令来改进应用性能的优化方式。
很多优化尝试着消除机器指令级别的跳转指令(如 x86体系结构中得JMP). 跳转指令将改变指令指针寄存器,从而转移程序执行流。这种跳转指令相对其他ASSEMBLY指令来说是一种很耗资源的命令。这就是为什么我们要减少或消除这种指令。代码嵌入就是一种很实用、很有名的消除转移指令的优化方式。因为执行跳转指令代价很高,所以将一些被频繁调用的小方法嵌入到函数体内将会带来很多益处。列表3-5证明了内嵌的好处。
列表3. 调用方法
int whenToEvaluateZing(int y){ return daysLeft(y)+ daysLeft(0)+ daysLeft(y+1);}
列表4. 被调用方法
int daysLeft(int x){ if(x ==0) return0; else return x -1;}
列表5. 内嵌方法
int whenToEvaluateZing(int y){
int temp =0;
if(y ==0)
temp +=0;
else
temp += y -1;
if(0==0)
temp +=0;
else
temp +=0-1;
if(y+1==0)
temp +=0;
else
temp +=(y +1)-1;
return temp;
}
在列表3-5中我们可以看到,一个小方法在另一个方法体内被调用了三次,而我们想说明的是:将被调用方法直接内嵌到代码中所花费的代价将小于执行三次跳转指令所花费的代价。
内嵌一个不常被调用的方法可能并不会带来太大的不同,但是如果内嵌一个所谓的“热”方法(经常被调用的方法)则可以带来很多的性能提升。内嵌后的代码常常还可以进行更进一步的优化,如列表6所示。
列表6. 代码内嵌后,更进一步的优化实现
int whenToEvaluateZing(int y){ if(y ==0)return y; elseif(y ==-1)return y -1; elsereturn y + y -1;}
循环优化
循环优化在降低执行循环体所带来的额外消耗方面起着很重要的作用。这里的额外消耗指的是昂贵的跳转、大量的条件检测,非优化管道(即,一系列无实际操作、消耗额外cpu周期的指令集)。这里有很多种循环优化,接下来列举一些比较流行的循环优化:
循环体合并:当两个相邻的循环体执行相同次数的循环时,编译器将试图合并这两个循环体。如果两个循环体相互之间是完全独立的,则它们还可以被同时执行(并行)。
反演循环: 最基本的,你用一个do-while循环来替代一个while循环。这个do-while循环被放置在一个if语句中。这个替换将减少两次跳转操作;但增加了条件判断,因此增加了代码量。这种优化是以适当的增加资源消耗换来更有效的代码的很棒的例子—编译器对花费和收益进行衡量,在运行时动态的做出决定。
重组循环体: 重组循环体,使整个循环体能全部的存储在缓存器中。