【Android】性能分析工具:开篇
There are known knowns; there are things we know we know. We also know there are known unknowns; that is to say we know there are some things we do not know. But there are also unknown unknowns – the ones we don't know we don't know.
——Donald Rumsfeld
目录
Android性能分析工具
性能分析层级
性能分析方法
计数器(Counter)
跟踪(Tracing)
剖析(Profiling)
小结
参考文献
Android性能分析工具
以下是一些我所知道的Linux/Android性能分析工具(欢迎补充):
- ps,top,vmstat,mpstat,iostat,netstat:Linux内核计数器。
- dumpsys: Android内核系统服务相关的计数器。
- ftrace: Linux内核提供的函数追踪工具。
- systrace,atrace: 基于ftrace的Android版性能追踪工具。
- perf:Linux上基于硬件性能监控单元(Performance Monitor Unit,PMU)的性能分析工具。
- linux-tools-perf:Android中的perf 。
- Simpleperf: Android上的Native CPU剖析分析工具。
- Android Stuido CPU Profiler:Android Studio提供的CPU剖析工具,本质上就是基于Simpleperf和systrace进行剖析。
- GPU Profiler: GPU剖析工具 // 如果我没理解错,应该是基于dumpsys gfxinfo。
- heapprofd:Android进程本地内存分析工具。
- Perfetto: 针对Linux / Android / Chrome平台和用户空间应用性能检测和跟踪工具。基于ftrace,atrace和heapprofd。
- Profilo: Facebook开发Android性能剖析库,用于在线收集应用程序的性能跟踪数据。
性能分析层级
下图是《DTrace》[1]书中提供的软件堆栈:
从编程语言角度,语言大致可以分为三类:
-
本地代码(Native Code):最常见如C和C ++,程序会被直接编译为在硬件上执行的本机代码。
-
解释型语言(Interpreted Code),也是我们常说的脚本语言(Scripting Language):代码在解释器下执行,解释器负责处理脚本的编译和执行。Perl和Shell就是脚本语言的典例。
-
字节码语言(Compiled Byte Code):介于本地代码和脚本之间,由特定于语言的编译器编译,不是本地代码而是字节码,最终的字节码由虚拟机或字节码解释器执行,比如Java。
从性能分析的角度,我们把解释型语言和字节码语言一同归于动态语言(Dynamic Languages)层,这是由于它们通常需要特定的语言工具来进行剖析和追踪。比如Java instrument提供的对JVM的监控。另外一些语言特性也为动态埋点提供了可能,最典型的就是基于Java Bytecode Injection的埋点技术,如ASM,Javassist。
内核(Kernel)管理着CPU调度、内存、文件系统、网络协议以及系统设备等,通过系统调用(System Calls)提供访问设备和内核服务。Libraries指系统库,较之系统调用,系统库提供的编程接口通常更为丰富和简单。如果操作系统允许,应用程序也可以直接进行系统调用(Android应该是不允许的,除非自己编译内核)。
对于Native Code,Libraries,System Calls以及Kernal,通常都是由系统层级提供的动态追踪(Dynamic tracing)工具进行分析,如ftrace,atrace等。
最低的一个层级,硬件——可编程硬件寄存器可以提供一些低层级的性能信息,包括CPU周期计数器、指令计数、缓存命中等等。Linux上通过perf_events接口或者perf_event_open()系统调用来访问这些计数器,perf中提供了大量基于PMU的计数。
分层看待问题不仅可以给我们一些线索:在哪个层级可以得到哪些信息,同时也提供了不同的视角:
- 自下而上的资源分析(Resource Analysis)视角:以对系统资源的分析为起点,涉及CPU、内存、磁盘、网卡、总线等等。着重分析使用率,判断某个资源是否已经处于极限或接近极限。
- 自上而下的工作负载分析(Workload Analysis)视角:着重检查应用程序性能,涉及应用程序吞吐量,延时等等。延时,也就是响应时间,通常是最重要的性能指标。
每种层级和视角都有对应的工具和自己独有的优势,尝试不同的层级,视角总能发现一些额外的信息。
性能分析方法
在《性能之巅》[2]中,Brendan Gregg大神将性能分析方法分为如下三类:
- 计数器(Counter)
- 跟踪(Tracing)
- 剖析 (Profiling)
计数器(Counter)
Linux内核中维护了各种各样的计数器,用于对事件计数。如磁盘I/O次数,系统调用次数,网络包的接收次数等等。
计数器在一定程序可以认为是“零开销”的,默认开启,由内核维护。唯一的使用开销是从用户空间读取它们,但也基本可以忽略不计。
内核计数器通常分为两个级别:
- 系统级别:vmstat,mpstat,iostat,netstat,sar
- 进程级别:ps,top,pmp
读取计数器的惯例通常是指定时间间隔和次数,如:
iostat 1 3
表示用一秒作为时间间隔,输出三次io信息:
第一行是机器自启动以来的均值,随后是每秒一次的快照。
跟踪(Tracing)
跟踪是我们最容易想到的性能观测方法,也就是跟踪收集每个事件的数据加以分析。比如最直观的,在函数开始和结束的地方记录系统时间,计算时间差得到函数的执行时间:
long startTime = System.nanoTime();// Some thing to tracelong duraion = System.nanoTime() - startTime();
一些在Android中可用于获取时间的API:
- System.currentTimeMillis()
- System.nanoTime()
- Debug.threadCpuTimeNanos()
- SystemClock.uptimeMillis()
- SystemClock.elapsedRealtime()
- SystemClock.elapsedRealtimeNanos()
跟踪捕获数据会有CPU开销,同时也需要一定的存储空间来存放数据。即使打印到系统日志中也是一样,因为日志本身也需要存储空间。系统日志本身也可以看做是一种形式的“跟踪”,毕竟每个日志就代表了每个事件的发生。跟踪框架(系统日志除外)一般默认是不开启的。
跟踪也有系统和进程级别:
- 系统级别:tcpdump,Dtrace,systemtap,perf
- 进程级别:starce,truss,gdb,mdb
剖析(Profiling)
剖析通过对目标收集采样或快照来归纳目标特征。最常见的例子就是CPU使用率,通过定期采样CPU状态进行剖析:
- 选择要采集的剖析数据及频率
- 每隔一定时间进行采样
- 等待兴趣事件发生
- 停止采样并收集数据
- 处理数据
“等待兴趣事件”不一定是必须的,更常见的是“全栈跟踪”,也就是对整个函数调用栈进行采样,可以得到完整的CPU代码路径,从更高的角度审视CPU用量。但这样做通常会产生超大量的数据。典型工具:Simpleperf, Android Stuido CPU Profiler(基于Simpleperf和 Java instrument)以及Profilo。
基于剖析的原理,全栈剖析不可避免的有一些限制:
- 如果函数执行时间过短,有可能不会被采集到
- 不能得到精确的CPU运行时间
- 非Native同语言通常需要有特定的语言剖析工具支持
可以想象,在不同层级得到的函数调用栈是不同的。这也是为什么Simpleperf只能分析Native Code,而Profilo实现了自己的Java stack unwinder(堆栈展开器)以准确地追踪Java函数调用和CPU时间。一些Android剖析工具:
- 基于Java堆栈跟踪:Profilo,Android Stuido CPU Profiler
- 基于本地堆栈跟踪:Simpleperf
火焰图是最常见的可以可视化全栈剖析结果的方法:
虽然很强大很直观,但基于它的原理,我们可以知道它不是“精确”的,所谓的“执行时间”其实是采样计数:
- 每个框代表栈里的一个栈帧(Stack frame),可以理解为一次函数调用。
- 框的宽度表示函数或上级函数在CPU上运行的时间。这个时间是基于采样计数得到的,更宽的函数可能比更窄的函数运行慢,也可能只是因为调用过于频繁。
- X轴横跨整个采样数据,从左到右没有任何意义(不表示时间)。
- Y轴表示栈的深度,底部为祖先调用函数,顶部为CPU上执行的函数。
- 如果是多线程运行,而且抽样是并发的,计数得到的时间累加可能会超过总的运行时间。
当然,剖析在每次剖析是得到的全栈快照还是准确的。从一定程度上,剖析和计数器是一样的。计数器也没有真正的时间维度,只能通过采样得到在固定时间段内的统计信息。
小结
根据层级和方法,按我自己的理解(可能不完全正确),总结开篇提到的几种工具:
后续准备写一系列文章介绍几种不同的工具。如有遗漏,欢迎补充~
参考文献
- Brendan Gregg:Systems Performance: Enterprise and the Cloud
- Brendan Gregg,Jim Mauro:DTrace: Dynamic Tracing in Oracle Solaris, Mac OS X and FreeBSD
- https://developer.android.com/studio/command-line/dumpsys?hl=en
- https://developer.android.com/topic/performance/tracing/command-line
- http://www.brendangregg.com/perf.html
- https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/README.md
- https://docs.perfetto.dev/#/
- https://engineering.fb.com/android/profilo-understanding-app-performance-in-the-wild/
- https://www.ibm.com/developerworks/cn/java/j-lo-jse61/index.html
- https://openresty.org/posts/dynamic-tracing/
- https://www.baeldung.com/java-asm
- https://www.javassist.org/tutorial/tutorial.html
更多相关文章
- facebook的Android调试工具Stetho介绍
- Android的文本编解码工具类
- 19_利用android提供的HanziToPinyin工具类实现汉字与拼接的转换
- Android音频口数据通信开发;通过静态分析工具了解IPA实现 -- iOS/
- Android开发者必备的十个工具
- 自定义adapter 及其性能优化
- Android指纹登录工具类封装
- Android之DiskLruCache(缓存工具)
- Android构建工具Gradle知识1