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性能分析工具(欢迎补充):

  • pstopvmstatmpstatiostatnetstat:Linux内核计数器。
  • dumpsys: Android内核系统服务相关的计数器。
  • ftrace: Linux内核提供的函数追踪工具。
  • systraceatrace: 基于ftrace的Android版性能追踪工具。
  • perf:Linux上基于硬件性能监控单元(Performance Monitor Unit,PMU)的性能分析工具。
  • linux-tools-perf:Android中的perf 。
  • Simpleperf: Android上的Native CPU剖析分析工具。
  • Android Stuido CPU Profiler:Android Studio提供的CPU剖析工具,本质上就是基于Simpleperfsystrace进行剖析。
  • GPU Profiler: GPU剖析工具 // 如果我没理解错,应该是基于dumpsys gfxinfo。
  • heapprofd:Android进程本地内存分析工具。
  • Perfetto: 针对Linux / Android / Chrome平台和用户空间应用性能检测和跟踪工具。基于ftraceatraceheapprofd
  • Profilo: Facebook开发Android性能剖析库,用于在线收集应用程序的性能跟踪数据。

 

性能分析层级

下图是《DTrace》[1]书中提供的软件堆栈:

【Android】性能分析工具:开篇_第1张图片

从编程语言角度,语言大致可以分为三类:

  1. 本地代码(Native Code):最常见如C和C ++,程序会被直接编译为在硬件上执行的本机代码。

  2. 解释型语言(Interpreted Code),也是我们常说的脚本语言(Scripting Language):代码在解释器下执行,解释器负责处理脚本的编译和执行。Perl和Shell就是脚本语言的典例。

  3. 字节码语言(Compiled Byte Code):介于本地代码和脚本之间,由特定于语言的编译器编译,不是本地代码而是字节码,最终的字节码由虚拟机或字节码解释器执行,比如Java。

从性能分析的角度,我们把解释型语言和字节码语言一同归于动态语言(Dynamic Languages)层,这是由于它们通常需要特定的语言工具来进行剖析和追踪。比如Java instrument提供的对JVM的监控。另外一些语言特性也为动态埋点提供了可能,最典型的就是基于Java Bytecode Injection的埋点技术,如ASMJavassist

内核(Kernel)管理着CPU调度、内存、文件系统、网络协议以及系统设备等,通过系统调用(System Calls)提供访问设备和内核服务。Libraries指系统库,较之系统调用,系统库提供的编程接口通常更为丰富和简单。如果操作系统允许,应用程序也可以直接进行系统调用(Android应该是不允许的,除非自己编译内核)。
对于Native Code,Libraries,System Calls以及Kernal,通常都是由系统层级提供的动态追踪(Dynamic tracing)工具进行分析,如ftraceatrace等。

最低的一个层级,硬件——可编程硬件寄存器可以提供一些低层级的性能信息,包括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信息:

【Android】性能分析工具:开篇_第2张图片

第一行是机器自启动以来的均值,随后是每秒一次的快照。
 

跟踪(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(基于SimpleperfJava instrument)以及Profilo
基于剖析的原理,全栈剖析不可避免的有一些限制:

  • 如果函数执行时间过短,有可能不会被采集到
  • 不能得到精确的CPU运行时间
  • 非Native同语言通常需要有特定的语言剖析工具支持

可以想象,在不同层级得到的函数调用栈是不同的。这也是为什么Simpleperf只能分析Native Code,而Profilo实现了自己的Java stack unwinder(堆栈展开器)以准确地追踪Java函数调用和CPU时间。一些Android剖析工具:

  • 基于Java堆栈跟踪ProfiloAndroid Stuido CPU Profiler
  • 基于本地堆栈跟踪Simpleperf

火焰图是最常见的可以可视化全栈剖析结果的方法:

【Android】性能分析工具:开篇_第3张图片

虽然很强大很直观,但基于它的原理,我们可以知道它不是“精确”的,所谓的“执行时间”其实是采样计数:

  • 每个框代表栈里的一个栈帧(Stack frame),可以理解为一次函数调用。
  • 框的宽度表示函数或上级函数在CPU上运行的时间。这个时间是基于采样计数得到的,更宽的函数可能比更窄的函数运行慢,也可能只是因为调用过于频繁。
  • X轴横跨整个采样数据,从左到右没有任何意义(不表示时间)。
  • Y轴表示栈的深度,底部为祖先调用函数,顶部为CPU上执行的函数。
  • 如果是多线程运行,而且抽样是并发的,计数得到的时间累加可能会超过总的运行时间。

当然,剖析在每次剖析是得到的全栈快照还是准确的。从一定程度上,剖析和计数器是一样的。计数器也没有真正的时间维度,只能通过采样得到在固定时间段内的统计信息。
 

小结

根据层级和方法,按我自己的理解(可能不完全正确),总结开篇提到的几种工具:

【Android】性能分析工具:开篇_第4张图片

后续准备写一系列文章介绍几种不同的工具。如有遗漏,欢迎补充~
 

参考文献

  • 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

更多相关文章

  1. facebook的Android调试工具Stetho介绍
  2. Android的文本编解码工具类
  3. 19_利用android提供的HanziToPinyin工具类实现汉字与拼接的转换
  4. Android音频口数据通信开发;通过静态分析工具了解IPA实现 -- iOS/
  5. Android开发者必备的十个工具
  6. 自定义adapter 及其性能优化
  7. Android指纹登录工具类封装
  8. Android之DiskLruCache(缓存工具)
  9. Android构建工具Gradle知识1

随机推荐

  1. Android(安卓)MediaPlayer
  2. Android-Activity介绍
  3. 如何从apk中得到version code
  4. 途牛面试经历
  5. LocalBroadcastManager
  6. Android 基础:surfaceSurface、SurfaceHol
  7. 设置Android程序图标
  8. 关于 android 输入法 adjustPan无效的解
  9. Android OnGestureListener用法 识别用户
  10. Android Studio 配置Java 8 Lambda表达式