造成卡顿的原因有很多 最终会反映到CPU时间上
CPU时间分为两种:
1⃣️用户时间
执行用户态应用程序代码消耗的时间
2⃣️系统时间
执行内核态系统调用所消耗的时间 包括 I/O 锁 中断以及其他系统调用的时间
CPU性能
评价CPU的性能 需要看主频 核心数 缓存等参数
获取CPU信息:
// 获取 CPU 核心数
cat /sys/devices/system/cpu/possible
// 获取某个 CPU 的频率
cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq
卡顿问题分析指标
查看CPU的使用率 可以通过 /proc/stat
得到整个系统的CPU使用情况 通过 /proc/[pid]/stat
得到某个进程的CPU使用情况
proc/self/stat:
utime: 用户时间,反应用户代码执行的耗时
stime: 系统时间,反应系统调用执行的耗时
majorFaults:需要硬盘拷贝的缺页次数
minorFaults:无需硬盘拷贝的缺页次数
如果CPU使用率大于60% 表示系统处于繁忙状态 需要进一步分析用户时间和系统时间的比例
对于普通应用程序 系统时间不会长期高于30% 如果超过 应该进一步检查是否I/O过多 还是其他系统调用问题
相关工具:
top 命令 查看哪个进程是CPU的消耗大户
vmstat 命令 实时动态监视操作系统的虚拟内存和CPU
strace 命令 跟踪某个进程中所有的系统调用
除了需要查看CPU的使用率还需要查看CPU饱和度(线程排队等待CPU的情况 也就是CPU的负载情况)
CPU饱和度首先和应用的线程数有关 如果启动的线程过多 容易导致系统不断切换执行的线程 把大量的时间浪费在上下文切换 每一次CPU上下文切换都需要刷新寄存器和计数器 至少需要几十纳秒的时间
可以通过使用 vmstat
命令或者 /proc/[pid]/schedstat
文件来查看CPU上下文切换次数 特别需要注意 nr_involuntary_switches
被动切换的次数
proc/self/sched:
nr_voluntary_switches:
主动上下文切换次数,因为线程无法获取所需资源导致上下文切换,最普遍的是 IO。
nr_involuntary_switches:
被动上下文切换次数,线程被系统强制调度导致上下文切换,例如大量线程在抢占 CPU。
se.statistics.iowait_count:IO 等待的次数
se.statistics.iowait_sum: IO 等待的时间
通过uptime 命令可以检查 CPU在1分钟 5分钟 和15分钟内的平均负载 比如一个4核的CPU 如果当前平均负载是8 表明每个CPU又来了一个线程在运行 还有一个线程在等待
一般平均来负载应该控制在 0.7*核数 以内
另外一个影响CPU饱和度的是 线程优先级 线程优先级会影响Android系统的调度策略 主要由 nice
和 cgroup
类型共同决定 nice
值越低 抢占CPU时间片的能力越强 当CPU空闲时 线程的优先级对执行效率的影响不会特别明显 但是在CPU繁忙时 影响就非常大
关于线程优先级 需要注意是否存在高优先级的线程空等低优先级线程,例如主线程等待某个后台线程的锁
从应用程序的角度看 用户时间 系统时间 等待CPU的调度 都是程序运行花费的时间
Android卡顿排查工具
Traceview
systrace
等
从实现上分两个流派:
1⃣️ instrument
获取一段时间内所欲呕函数的调用过程 分析这个过程进一步分析优化的点
2⃣️ sample
有选择性或者采用抽样的方式观察某些函数的调用过程 分析可疑点
Traceview
利用Android Runtime
函数调用event事件 将函数运行的耗时和调用关系写入trace
文件中
属于instrument
可以查看整个过程有哪些函数调用 但是本身性能开销大
Android 5.0 之后 新增了startMethodTracingSampling
方法 使用基于样本的方式进行分析 以减少对运行时的性能影响Nanoscope
instrument
类型的性能分析工具 性能损耗较小
原理是 直接修改Android虚拟机源码 在ArtMethod
执行入口和执行结束位置增加卖点代码 将所有的信息先写到内存 等到trace结束后才统一生成结果文件
限制:
1⃣️ 需要自己刷ROM 并且当前只支持Nexus 6P 或者对应的模拟器
2⃣️ 默认只支持主线程采集 其他线程需要代码手动设置
非常适合做启动耗时时的自动化分析
生成的是符合Chrome tracing
规范的HTML文件 可以通过脚本来实现两个功能:
1⃣️ 反混淆 通过mapping自动反混淆结果文件
2⃣️ 自动化分析 传入相同的起点和终点 实现两个结果文件的diff 自动分析差异systrace
可以跟踪系统的I/O操作 CPU负载 Surface渲染 GC等
利用了Linux的ftrace
调试工具 相当于在系统各个位置添加了一些性能探针
Android 在ftrace的基础上封装了atrace
增加了更多特有的探针 如Graphics、Activity Manager、Dalvik VM、System Server
只能监听特定系统调用的耗时情况 所以属于sample
类型 性能开销很低 不支持应用程序代码的耗时分析
- Simpleperf
分析Native
函数的调用 属于sample
类型 性能开销很低
利用CPU的性能监控单元(PMU)提供的硬件perf事件
使用Simpleperf
可以看到所有的Native代码的耗时 例如加载dex vertify class
Simpleperf
同时封装了systrace
的监控功能
发展的几个阶段:
第一个阶段:在Android M和以前 Simpleperf
不支持Java代码分析
第二个阶段:在Android O和以前 需要手动编译OAT文件
第三个阶段:在Android P以后 Simpleperf
支持Java代码分析
除了Nanoscope
之外的工具都只支持debugable
的应用程序 需要root才能测试release包
总结 如果需要分析Native代码的耗时 可以选择Simpleperf;如果想分析系统调用 可以选择systrace;如果想分析整个程序执行流程的耗时 可以选择Traceview或者插桩版本的systrace
可视化方法
Android Studio 3.2 的Profiler中集成了几种性能分析工具:
-
Sample Java Methods
的功能类似于Traceview
的sample
类型 -
Trace Java Methods
类似Traceview
的instrument
类型 -
Trace System Calls
类似systrace
-
SampleNative(API Level 26+)
类似Simpleperf
这些工具都支持Call Chart
和 Flame Chart
两种展示方式
-
Call Chart
Traceview 和 systrace 默认使用的展示方式 按照应用程序的函数执行顺序来展示 适合分析整个流程的调用 -
Flame Chart
火焰图 以全局视野看待一段时间的调用分布s s s