1.perfetto网站
https://ui.perfetto.dev/
2.快捷键
3.线程状态分析
Runnable表示线程正在运行或者等待CPU执行
Runnable (Preempted)表示线程正在运行,但在运行过程中被其他高优先级线程抢占
Running表示线程正在运行
Uninterruptible Sleep Uninterruptible Sleep (IO)不可中断的休眠状态,IO阻塞
Uninterruptible Sleep (non-IO)不可中断的休眠状态,非IO导致,在等内核锁。通常是低内存导致等待、各种各样的内核锁。
Sleeping表示线程正在休眠或者等待某个条件满足
3.1 Running 时间长
3.1.1 代码本身复杂度高,执行耗时久
这是最常见的一种方式,当然不排除平台有bug,有时候厂商在libc、syscal等高频核心函数,加了一些逻辑导致了代码运行时间长。
优化建议: 优化逻辑、算法,降低复杂度。为了进一步判断具体是哪个函数耗时,可使用 AS CPU Profiler、simpleperf,或者自己通过 Trace.begin/end() API 添加更多 tracepoint 点
当然不排除有的时候平台有bug,在关键的libc或内核函数加了一些逻辑
3.1.2 代码以解释方式执行
Trace 中看到 「Compiling」字眼时可能意味着它是解释执行方式进行。刚安装的应用(未做 odex)的程序经常会出现这种情况
优化建议: 使用 dex2oat 之后的版本试试,解释执行方式下的低性能暂无改善方法,除非执行 dex2oat 或者提高代码效率本身
除此之外,使用了编程语言的某种特性,如频繁的调用 JNI,反复性反射调用。除了通过积攒经验方式之外,通过工具解决的方法就是通过 CPU Profiler、simpleperf 等工具进行诊断
3.1.3 线程跑小核,导致执行时间长
对 CPU Bound 的操作来说跑在小核可能没法满足性能需求,因为小核的定位是处理非UX 强相关的线程。不过 Android 没办法保证这一点,有时候任务就是会安排在小核上执行。
优化建议:线程绑核、SchedBoost 等操作,让线程跑尽量跑更高算力的核上,比如大核。有时候即使迁核了也不见效,这时候要看看频率是否拉得足够高,见“原因 4”
3.1.4线程所跑的大核运行频率太低
优化建议:
优化代码逻辑,主动降低运行负载,CPU 频率低也能流畅运行
修改调度器升频相关的参数,让 CPU 根据负载提频更激进
用平台提供的接口锁定 CPU 频率(俗称的「锁频」)
3.1.5 温升导致 CPU 关核、限频
优化建议:
手机因结构原因导致散热能力差或温升参数过于激进时,为了保护体验跟不烫伤人,几乎所有手机厂家的系统会限制 CPU 频率或者直接关核。排查思路是首先需要找到触发温升的原因。
温升的排查的第一步,首先要看是外因导致还是内因导致。外因是指是否由外部高温导致,如太阳底下,火炉边;往往夏天的时候导致手机发热的情况越严重
内因主要由 CPU、Modem、相机模组或者其他发热比较厉害的器件导致的。以 CPU 为例,如果后台某个线程吃满 CPU,那就首先要解决它。如果是前台应用负载高导致大电流消耗,同样道理,那就降低前台本身的负载。其他器件也是同样道理,首先要看是否是无意义的运行,其次是优化业务逻辑本身
除此之外,温升参数过于激进的话导致触发限频关核的概率也会提高,因此通过与竞品对比等方式调优温升参数本身来达到优化目的
3.1.6 CPU 算力弱
优化建议:
ARM 处理器在相同频率下不同微架构的配置导致的性能差异是非常明显的,不同运行频率、L1/L2 Cache 的容量均能影响 CPU 的 MIPS(Million Instructions Per Second) 执行结果。
优化思路有两条:
编译器参数
优化代码逻辑
第一条比较难,大部分应用开发者来说也不太现实,系统厂商如华为,方舟编译器优化 JNI 的思路本质是不改应用代码情况下提高代码执行效率来达到性能上的提升
第二条可以通过 simpleperf 等工具,找到热点代码或者观察 CPU 行为后做进一步的改善,如:
Cache miss 率过高导致执行耗时,就要优化内存访问相关逻辑
代码复杂指令过多导致耗时,就要优化代码逻辑,降低代码复杂度
设计好业务缓存,尽量提高缓存命中率,避免抖动(反复地申请与释放)
3.2 Runnable 过长
3.2.1 优先级设置错误
应用设置了过高的优先级:至于抢占了其他线程的任务,对后者来说显得自己优先级太低了。
应用设置了过低的优先级:当此线程处于「关键链路」时,以 Runnable 执行的概率就越高,导致卡顿概率也高。
系统出 Bug 时把线程优先级设为过高或者过低。
优化思路:
应用视情况调整线程优先级,可从 Trace 中可以看到是被哪个线程抢占了。
系统将关键线程调度策略设置成 FIFO。
我们在实践中见到过不少应用因为设置错了优先级反而导致更卡。原因比较复杂,可能开发者所使用的机器用当时的优先级策略没问题,但是在别的厂商的调度器(头部大厂基本都有自己改动调度器)下就会出现水土不兼容的情况。一般情况下,三方应用开发者不建议直接调用这类 API,弄巧成拙,屡见不鲜。
长远看来更靠谱的方式是合理安排自己的任务模型,不要把对实时性要求很高的任务放到 worker 线程上。
3.2.2 绑核不合理
有时候为了让线程运行得更快,会把线程绑定到大核,在前面解决 Running 时间长时也有建议绑大核,但是绑核一定要谨慎,因为一旦把线程绑定在某个核,表示线程只能运行在这个核上即使其它核很空闲。如果多个线程都绑定在某个核,当这个核很繁忙调度不过来时,这些线程就会出现 Runnable 时间很长的情况。所以绑核一定要谨慎!下面是绑核需要注意的一些事项:
线程绑核不要绑定在单个核上,这样容错率会特别低,因为一旦这个核被其它线程抢占绑定这个核的线程就要等着,所以尽量以 CPU 簇为单位进行绑核,比如线程要绑定大核,可以指定 4-7 大核而不是指定某个一大核。
2 个大核平台尽可能减少绑定大的核线程数目,不然会使得大核很容易繁忙,把绑核会变成「负优化」。
要正确区分大小核,比如 8 个核的平台,4-7 不一定就是大核,有的平台可能 0-3 才是大核。
只能在 CPUSET 允许范围内绑核,如果 CPUSET 只允许进程跑 0-3,如果进程试图绑定在 4-7 会绑核失败,甚至会有一些意料之外的致命错误。
3.2.3 软件架构设计不合理
重申下,Runnable 是指在 CPU 核上的排队耗时,按常识可可知道排队长、频繁排队时出问题概率也就越高。一个绘制任务所依赖的线程数量越多,出问题的概率也越高,因为排队次数变多了嘛。
软件架构不止要满足业务需求,也要在性能、扩展性方面上做思考,从上面推导可知,如果你程序编程模型需要大量线程协同运行来完成关键操作,如绘制,那出问题的概率就越高。
最常见的有,两个线程之间有频繁的有通讯与等待(线程 A 把任务转移到线程 B 执行,A 等待 B 任务执行完后被唤醒), CPU 繁忙时很容易打出 Runnable 等待状态,CPU 越忙概率越高。
优化思路:
应用调整线程优先级,见「原因 1」。
优化代码架构/逻辑,免频繁等待其他线程的唤醒,在 Trace 中可以看到线程的依赖关系。可借助 CPU Profiler 探查代码执行逻辑,提高分析唤醒关系的效率。
平台通过修改调度器来识别有关系链的线程组,优先调度这个组里的线程。
3.2.4 原因 4: CPU 算力限制、锁频、锁核、状态异常
排队做核酸检测一样,检测窗口多的队列排队时间少。CPU 算力差、关核、限频,导致 Runnable 的概率也更高。通常的原因有:
场景控制
不同场景模式下的不同频率、核心策略
高温下的锁频锁核
CPU 省电模式:如高通的 Low Power Mode。
CPU 状态切换:如 C2/C1 切换到 C0 耗时久。
CPU 损坏,概率小但也有可能会出现。
低端机 :安卓上的低端机。
其中:
原因 1 场景控制, 考验厂家的能力与各自的标准,应用程序能做的还是那句名言 → 降低自己负载,少惹平台。 厂家为了设计好「场景控制」,需要有精细化的场景识别与合理的控制能力,将功耗与性能的平衡做到全局最优化,不同场景下应突出不同的业务能力,而不是一杆子拍死。
高温下的优化建议请参考「Perfetto/Systrace: 不同 CPU 运行状态异常原因 101 - Running 长」中的「原因 5: 温升导致 CPU 关核、限频」。
原因 3 CPU 状态切换 是芯片固有的特性,出现的概率小,但也不是不可能,每个芯片架构升级换代的时候就时不时遇到「妥协」版的 CPU 产品。厂家对芯片的评估是个比较隐性的能力,很少会被大众提及,但是非常重要的一个能力。电子消费品历史中,也总是重演关键器件选错了,导致厂家走入万劫不复境地的真实案例。
原因 5,安卓上的低端机,真的就指配备里低算力的 CPU,这与苹果的做法不一样,它的 CPU 至少跟当期旗舰是一样的。同样参考 「Perfetto/Systrace: 不同 CPU 运行状态异常原因 101 - Running 长」中的「原因 6: 算力弱」。
3.2.5 调度器异常
几乎所有的厂家都做了调度器优化方面的工作,虽然概率小,但也有可能会出异常。场景锁频锁核机制有问题、内核各种 governor 的出问题的时候,会出现明明 CPU 的其他核都很闲,但任务都挤在某几个核上。
系统开发者能做的就是把基础「可观测性技术」建好,出问题时可以快速诊断,因为这类问题一是不好复现,二是现象出现时机较短,可能立马就恢复了。
3.2.6 处理器区分执行 32 位与 64 位进程
有些过渡期的芯片,如最近推出的骁龙 8Gen1 与 天玑 9000,会有非常奇葩的运行限制。32 位的程序只能运行某个特定微架构上,64 位的则畅通无阻。且先不说这种「脑残设计」是处于什么所谓「平衡」,他带来的问题是,当你用的应用大量还是 32 位的时候,很多任务(以进程为单位)都挤在某个核心上运行,结合前面的理论,都挤在一起,出现 Runnable 的概率就更高。
对应用开发者,建议尽快升级至 64 位程序。如果你用的是第三方方案,尽早通知改进或者改用其他方案。
对系统开发者,一是根据问题联系应用厂商做更新,二是特殊加强后台管理功能,进一步降低 32 位程序的运行负载。
4.查看Scheduling wakeups
选中CPU的Running状态
然后切到CPU Slice,就可以看到wakeup关系
5.CPU Usage
圈出需要分析的CPU区域
cpu 使用率 = (Wall duration)/ (cpu 总核数) / (Selected range)
参考文章:
1.第35讲 Perfetto 分析CPU 调度 - Android Camera性能分析
2.Systrace 线程 CPU 运行状态分析技巧 - Running 篇
3.Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇