函数嵌套调用性能好还是分开写性能好?
下午突然想到一个问题:
* 形式如下的两种方法,哪一种的效率更高一点?
第一种:
A=fun1(args1[]);
B=fun2(A,args2[]);
C.fun3(B);
第二种:
C.fun3(fun2(fun1(args1[]),args2[]));
也就是说,一段结果相同的代码,是将中间使用的函数嵌套起来写性能更好还是分开写性能更高呢?
这里假定变量不会再被后面的代码使用,不存在复用的问题,同时,也将可读性问题暂放一边,仅仅从性能角度考虑。
直觉上,应当是第二种形式的性能更高一些,减少了中间变量存储,但是我还是有一点迷惑:函数调用时系统要保存现场,对各种变量在栈中进行保存,调用完了还要恢复现场,恢复各种上一个方法中的值,第二种形式是不是在这方面消耗了更多性能呢?毕竟在第二种形式中,刚想调用fun3,发现还要调用fun2,刚想调用fun2,发现还必须先调用fun1。
本着先敲一敲看看的想法,我用java写了一个测试代码(放到文章最后面),从javap反编译的中间代码上能看出上面两个问题的答案:
第一种形式:
第二种形式:
从代码上,可以看出,第一种形式的确会多保存两个临时变量,多了成对的两个命令’astore’、’aload’从寄存器中存读变量。
而第二种形式在编译成中间代码之后,函数的调用顺序也变成了fun1->fun2->fun3,而且没有中间变量。
后来我又想了想,其实第一种形式和第二种形式函数调用对栈的影响次数是一样的,即便第二种函数切换频繁,在机器层面也是按部就班地做,性能上应当相同。
结论:
第二种形式在性能上更优题外话:
- 在看反编译的中间代码时我发现,即便普通的字符串拼接,到了java字节码的层次,也是通过StringBuilder完成的,也就是说简单的字符串拼接,性能上应当和使用StringBuilder一样,字符串拼接在这里不会形成性能瓶颈,但是也有例外。
比如,我在下面的测试代码里用的那样,在for循环中循环字符串拼接,这个时候会反复调用StringBuilder的toString方法,导致产生大量的String挤占堆空间,结果我的测试代码就显示OOM了,所以如果要使用以下代码请调节循环次数。 - 从1中可以看出,其实我测试代码有问题,本来想找个耗时的操作区分两种形式代码的性能,所以使用了String的拼接,结果循环次数一多就OOM,也就是说我没从实践中真实测得两种形式代码的差别(即便循环次数少的情况下,多次测量结果也是千奇百怪,我怀疑系统对程序的调度影响了耗时)
- 在看反编译的中间代码时我发现,即便普通的字符串拼接,到了java字节码的层次,也是通过StringBuilder完成的,也就是说简单的字符串拼接,性能上应当和使用StringBuilder一样,字符串拼接在这里不会形成性能瓶颈,但是也有例外。
以下我写的测试代码:
public class AnyIdeaTestFirst{
/**
* 静态内部类,用于返回一个记录方法耗时和字符串的集合
*/
static class Iner{
String s;
long time;
public String getS() {
return s;
}
public void setS(String s) {
this.s = s;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
}
public static Iner fun1(String a){
long timeBefore=System.currentTimeMillis();
for(int i=0;i<50;i++){
a+=a;
}
Iner iner=new Iner();
iner.setS(a);
long timeAfter=System.currentTimeMillis();
iner.setTime(timeAfter);
System.out.println("fun1 after:"+(timeAfter-timeBefore));
return iner;
}
public static Iner fun2(Iner iner,String a){
long timeBefore=System.currentTimeMillis();
System.out.println("进入fun2,与退出fun1之间的时间差:"+(timeBefore-iner.getTime()));
for(int i=0;i<30;i++){
iner.setS(a);;
}
long timeAfter=System.currentTimeMillis();
iner.setTime(timeAfter);
System.out.println("fun1 after:"+(timeAfter-timeBefore));
return iner;
}
public static void fun3(Iner iner){
long timeBefore=System.currentTimeMillis();
System.out.println("进入fun3,与退出fun2之间的时间差:"+(timeBefore-iner.getTime()));
}
public static void main(String []args){
System.gc();
System.out.println("方法一开始-------------------------------"+System.currentTimeMillis());
Iner iner1=fun1("a");
Iner iner2=fun2(iner1,"b");
fun3(iner2);
System.out.println("方法一结束--------------------------------"+System.currentTimeMillis());
System.out.println("方法二开始--------------------------------"+System.currentTimeMillis());
fun3(fun2(fun1("c"),"d"));
System.out.println("方法二结束--------------------------------"+System.currentTimeMillis());
}
}