软件性能优化是一个很大的概念。首先从自带的一些工具开始,利用工具来协助我们对性能做优化。
解决系统性能问题的几个主要步骤是:找->定->调
找:要优化肯定要先找下那部分有性能问题啊,例如有用户投诉所APP卡,你和他沟通后,他说了在主界面滑动过程卡顿。这样我们就大致找到一个需要优化的范围点,接下来需要定位啊。当然这是游击,碎片化的方式,最好还是对程序进行大量的有针对性的测试,得到测试数据,然后根据需要定调整目标,要求达到一个怎样的优化效果,例如启动时间由2310ms编程1320ms等等。
定:找到了问题的大致方向,需要更具体的分析系统瓶颈,分析测试数据,找到其中的bottleneck。
调:找到瓶颈所在,当然需要对bottleneck的代码做优化。
关于瓶颈,一般而言主要就这样三类:
低频高耗:函数被调用次数不多,但每次调用却花费很长时间。
高频低耗:指那些函数自身占用时间不长,但调用却非常频繁的函数。
高频高耗:那被调用频繁,且函数本身耗费长的,这绝对重点关注对象啊。
怎么去找到这些瓶颈,可以使用TraceView来实现
套路如前面一样,先点击Start Method Profile
按钮,然后我们分别点这两个函数对应的按钮,让程序帮我们记录下载先。运行后的效果如下图
点击查看大图
TraceView介绍
现在就需要来介绍下这个界面的内容了
整个界面主要分两个大块,顶部由A和C构成的时间线面板(Timeline Panel)和下面的分析面板(Profile Panel)。
Tips:双击上面的函数信息那一栏可以缩小,鼠标选中线程的颜色部分轻微水平拉动可以放大图颜色脉冲bar的高度表示cpu的利用率,高度越高表示cpu利用率越高白色gap空白块表示该线程目前没有占CPU,被其他线程占用黑块表示系统空闲(system idle)
我们细看下顶部的时间板,在放大后,当我们鼠标停在某个刻柱的时候,顶部会显示对应时刻,系统在运行的程序信息。左边表示对应的线程信息。如果你点击某个刻度的柱子,对应的柱子也会动一下!
由图可得,在2509.181msec的时候,我们的CPU在运行着我们的simplyfunc()
函数 下面一行的 excl cpu msec, incl cpu msec, excl readl msec等的时间信息会在后面细说。
下面的模板信息就很多啦。我们看下顶栏,现在对主要的各个属性的意思如下面表格
列名
描述
Name
该线程运行过程中所调用的函数名
Incl Cpu Time
某函数占用的CPU时间,包含内部调用其它函数的CPU时间
Excl Cpu Time
某函数占用的CPU时间,但不含内部调用其它函数所占用的CPU时间
Incl Real Time
某函数运行的真实时间(以毫秒为单位),内含调用其它函数所占用的真实时间
Excl Real Time
某函数运行的真实时间(以毫秒为单位),不含调用其它函数所占用的真实时间
Call+Recur Calls/Total
某函数被调用次数以及递归调用占总调用次数的百分比
Cpu Time/Call
某函数调用CPU时间与调用次数的比。相当于该函数平均执行时间
Real Time/Call
同CPU Time/Call类似,只不过统计单位换成了真实时间
Incl Cpu Time %
表示以时间百分比来统计的Incl Cpu Time
在明白了这些名字的意思后,现在我们可以用Traceview来查找我们的瓶颈了!
找瓶颈
高频低耗
先来看下我们的高频低耗类的,根据定义,他就是调用得多的那个家伙。在我们前面关于列明的介绍中,有一列就是Call+Recur Calls/Total,他描述被调用的次数。因此我们先按照这个属性来排个序,从高到低的。排序结果如下图:
次数 ,我们看到,我们的simplyFunc被用了666次,很正确。很好,这样看下他的上面几个667次的,我们的SimplyFunc内容为拼接字符串然后打印日志,所以与String相关的几个函数也被调用了很多次。
private void simplyFunc() { String a = "hello "; String b = "world"; Log.e(TAG, "simplyFunc() print= " + a + b);}
直觉告诉我们,编译器有可能把第三行代码用一个StringBuilder来处理了。
时间,我们是确定这个函数确实调用次数多很高频,但这不代表人家耗时,所以我们看下右边的关于霸占的CPU时间,我们看到他的Incl Cpu Time为37.183,占了总数的75.9%。很好,这样我们就确认这个函数是一个瓶颈,是我们需要花时间来处理的。
到这里基本我们确定了,完成了找的步骤。现在需要完成的是定的步骤,确定下瓶颈实际在哪里。我们再看下图,在我们的函数展开的下面,有些内容,我们需要看下。
Parents : 这个表示调用这个函数的父方法,就是指是onRepeatClick
调用了这个函数。
Children:相对于Parents,这个就是他调用的。
我们看它耗费的时间多的是去调用StringBuilder去了。嗯,我们明明没调用这个类,为何他耗费时间去和Stringbuilder玩去了呢?因为这是编译后的运行效果,编译器会做些手脚嘛。
根据Children的信息,告诉我们SimplyFunc主要耗费的时间都去做了什么去了,这样我们可以根据这些信息,对我们的函数做针对性的优化内容。你也可以一直点children
里面最耗费时间的,一直跟进去,直到你觉得找到答案为主。
细看函数
上面的整流程是相对粗糙的,因为整个下面的面板还有很多我们不关心的内容,有时函数多起来就找的麻烦,为了细化范围,这里提供另外的方案
用Debug.startMethodTracing()
和Debug.stopMethodTracing()
方法,当我们再函数加多这对函数,运行完这段代码后,在/sdcard
路径就会有一个trace文件生成。另外也可以调用startMethodTracing(String traceName)
设置trace文件的文件名,最后你可以用adb pull /sdcard/test.trace /tmp
命令将trace文件复制到你的电脑中,然后像刚才那样,用DDMS工具打开。
用这种更精确的方法,虽然罗嗦些,但非常适合检测某一个方法的性能,颗粒度更细。
public void onRepeatClick(View view) { Debug.startMethodTracing("simplyFunc"); for (int i = 0; i < 666; i++) { simplyFunc(); } Debug.stopMethodTracing();}