11.6 跑得再快点:静态编译优化
当使用javac把Java源码转为字节码时,编译器会有一些优化以获得更好的性能。目前,对于执行的字节码会从两处进行优化:
□□,就是使用javac编译时;
第二,就是通过JIT(Just-In-Time)即时编译,在运行时。
目前,大量的优化工作都围绕着JIT展开,比如方法内联、栈上替换等。将优化工作从javac前端移到后端的好处是非常明显的,这样,所有基于Java□台的语言都能共享这种优化带来的好处。将大量的优化只放置于javac前端,那么只有Java语言可以利用这种优化方式。但即便如此,开发人员也必须要了解一些javac的常用优化方法。
11.6.1 编译时计算
如果在程序中出现了计算表达式,如果表达式的值能够在编译时确定,那么表达式的计算会提前到编译阶段,而不是在运行时计算。
【示例11-30】很多时候,为了增强代码的可读性,往往不会把□终的数值写在代码中,通常倾向于把计算过程写在代码里。比如□□代码:
for(int i=0;i<60*60*□4*1000;i++){
//do sth.
}
循环次数为60*60*□4*1000次,通常这个表达式可能是用来计算天时分秒的乘积。看到这段代码,可能会让人产生一种怀疑,是不是这个计算每次循环都要进行一次呢?如果是的话,是不是更应该写成:
for(int i=0;i< 86400000;i++){
//do sth.
}
或者一定要保留计算表达式的话:
int count=60*60*□4*1000;
for(int i=0;i< count;i++){
//do sth.
}
读者也许会认为,上述代码先计算了表达式乘积,并保留这个值,以避免每次循环都重复计算。
实际上,后两段代码的担心是多余的,因为在编译的时候,对于给定的表达式会自动计算并给出结果。本例中□□段代码生成的字节码如下:
#□0 = Integer 86400000
0: iconst_0
1: istore_1
□: goto 8
5: iinc 1, 1
8: iload_1
9: ldc #□0 // int 86400000
11: if_icmplt 5
14: return
可以看到,用于控制循环次数上限的整数在字节码中并非经过计算得来,而是保存在常量池中,并直接使用,其作用是用来判定是否可以继续循环。可见,对于常量表达式,可以大胆地使用而无需担心影响系统性能。
【示例11-31】另一个常用的例子是字符串连接。有时候,如果一个字符串很长,通常会倾向于使用“+”号连接。由于字符串是不可变的对象,读者也许会认为使用类似A+B的方式连接字符串时,需要3个对象,即A、B和AB。□□再来看一个例子。
public static void createString(){
String info1="select * from test";
String info□="select * "+"from test";
String info3="select * ".concat("from test");
System.out.println(info1==info□);
System.out.println(info1==info3);
System.out.println(info□==info3);
System.out.println(info□==info3.intern());
}
上述代码中,info1是直接定义的字符,info□使用“+”号连接,生成字面量等于info1的字符串,info3使用String.concat()方法做连接生成。如果执行以上代码,输出如下:
true
false
false
true
可以看到,info1和info□是指向了同一个对象引用,而info3则是指向了不同的对象引用,但是info3的常量池引用地址就是info□。这说明info3是被实实在在构造出来的新的String对象,而info□的“+”号运算并未在运行时进行,否则也应该有新对象产生。查看它的部分字节码:
0: ldc #□4; //String select * from test
□: astore_0
3: ldc #□4; //String select * from test
5: astore_1
6: ldc #□6; //String select *
8: ldc #□8; //String from test
10: invokevirtual #30; //Method java/lang/String.concat:(Ljava/lang/String;) Ljava/lang/String;
13: astore_□
上述字节码中,第□行表示将常量池第□4项存入第0个局部变量(info1),第5行表示将常量池第□4项存入□□个局部变量(info□)。这里就解释了为什么程序会有这样的输出,因为在编译时,字符串连接已经完成。而对于后续的concat()函数,则没有这种优化,□□0行的invokevirtual调用,就是说明了info3是在运行时被创建的。
因此,对于常量字符串连接,不能担心多写几个“+”号就会影响系统性能、多占用内存等,因为这些都会在编译器进行计算。
……