Android Performance

Android Perfetto 系列 (九) - CPU 信息解读

Word count: 7.1kReading time: 26 min
2025/11/12
loading

本文是 Perfetto 系列的第九篇文章,主题是 Perfetto 中的 CPU 信息分析。Perfetto 提供了远超 Systrace 的数据可视化与分析能力,理解 CPU 相关信息是定位性能瓶颈、分析功耗问题的基础。

本系列的目标,就是通过 Perfetto 这个工具,从一个全新的图形化视角,来审视 Android 系统的整体运行,同时也提供一个学习 Framework 的新途径。或许你已经读过很多源码分析的文章,但总是对繁杂的调用链感到困惑,或者记不住具体的执行流程。那么通过 Perfetto,将这些流程可视化,你可能会对系统有更深入、更直观的理解。

本文目录

Perfetto 系列文章

  1. Android Perfetto 系列目录
  2. Android Perfetto 系列 1:Perfetto 工具简介
  3. Android Perfetto 系列 2:Perfetto Trace 抓取
  4. Android Perfetto 系列 3:熟悉 Perfetto View
  5. Android Perfetto 系列 4:使用命令行在本地打开超大 Trace
  6. Android Perfetto 系列 5:Android App 基于 Choreographer 的渲染流程
  7. Android Perfetto 系列 6:为什么是 120Hz?高刷新率的优势与挑战
  8. Android Perfetto 系列 7 - MainThread 和 RenderThread 解读
  9. Android Perfetto 系列 8:深入理解 Vsync 机制与性能分析
  10. Android Perfetto 系列 (九) - CPU 信息解读
  11. 视频(B站) - Android Perfetto 基础和案例分享

Perfetto 中的 CPU 信息概览

在 Perfetto UI 中,CPU 相关的信息通常分组置于顶部,是性能分析的起点。主要包含以下三个核心轨道:

  • CPU Scheduling (CPU 调度): 显示在每个时间点,各个 CPU 核心上正在执行的线程。
  • CPU Frequency (CPU 频率): 显示每个 CPU 核心或核心簇的频率变化情况。
  • CPU Idle (CPU 空闲状态): 显示每个 CPU 核心进入的低功耗状态 (C-States)。

image-20251112214415666

通过分析 CPU 相关的信息,可以解答以下关键性能问题, 或者进行竞品分析:

  • 应用主线程为何没有执行?是否被其他线程抢占?
  • 某个任务执行缓慢的原因是什么?是否被调度到了低性能核心?
  • 在特定场景下,CPU 频率是否受限?
  • 应用在后台时,CPU 是否有效进入了深度睡眠状态?

抓取 CPU 信息所需要的 Trace Config

为了采集到本文分析所需的所有 CPU 数据,你需要一个正确的 TraceConfig。不正确的配置会导致某些轨道(如 CPU 频率)或某些内核事件(如唤醒事件)丢失。以下是 Perfetto 官方文档推荐的、用于通用 CPU 分析的配置。你可以将其添加到你的 Perfetto 的 Config 中,抓 Trace 的时候使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "sched/sched_process_exit"
ftrace_events: "sched/sched_process_free"
ftrace_events: "task/task_newtask"
ftrace_events: "task/task_rename"
ftrace_events: "sched/sched_switch"
ftrace_events: "power/suspend_resume"
ftrace_events: "sched/sched_blocked_reason"
ftrace_events: "sched/sched_wakeup"
ftrace_events: "sched/sched_wakeup_new"
ftrace_events: "sched/sched_waking"
ftrace_events: "sched/sched_process_exit"
ftrace_events: "sched/sched_process_free"
ftrace_events: "task/task_newtask"
ftrace_events: "task/task_rename"
ftrace_events: "power/cpu_frequency"
ftrace_events: "power/cpu_idle"
ftrace_events: "power/suspend_resume"
symbolize_ksyms: true
disable_generic_events: true
}
}
}
data_sources {
config {
name: "linux.process_stats"
process_stats_config {
scan_all_processes_on_start: true
}
}
}
data_sources {
config {
name: "linux.sys_stats"
sys_stats_config {
cpufreq_period_ms: 250
}
}
}

这个配置启用了包括 sched(调度)、power(频率和空闲)、task(任务生命周期)在内的关键 ftrace 事件,是进行深度 CPU 分析的基础。

CPU 核心架构:big.LITTLE

在深入分析前,必须了解现代手机 SoC 的 CPU 核心架构。目前主流的移动处理器普遍采用 big.LITTLE 异构多核架构,或其变种,如 big.Medium.LITTLE(大中小核)。

  • 小核 (LITTLE cores): 针对低功耗设计,频率较低,用于处理后台任务、轻量级计算,以保证续航。
  • 大核 (big cores): 针对高性能设计,频率更高,功耗也更大,用于处理用户交互、游戏、应用启动等重负载场景。
  • 超大核 (Prime Core): 部分旗舰芯片会有一个频率极高的超大核,用于应对最严苛的单核性能挑战。

在 Perfetto 的 CPU 轨道中,核心通常从 0 开始编号。例如,在一个典型的八核处理器中,CPU 0-3 可能为小核,CPU 4-6 为大核,CPU 7 为超大核。识别核心类型对于性能分析至关重要:一个计算密集型任务如果长时间运行在小核上,其耗时必然远超预期。分析时,需要将线程的运行核心与其任务属性进行匹配,以判断调度器(Scheduler)的行为是否符合预期。

下面是一个典型的 4+4 的 CPU :

image-20251112214626810

下面是一个典型的 4+3+1 的 CPU ( MTK 的天玑 9500、9400 ,以及高通骁龙高端系列):

image-20251112215105550

下面是一个 5+2 的 CPU (高通 8Elite 1 阉割版,8Elite 1 的标准版 0-5 是小核,6-7 是大核,这里就不放图了)

image-20251112214833158

一般通过查阅 CPU 的 spec 就可以知道他的大中小核心的架构,或者 cat 对应的 CPU 节点也可以,这里就不再赘述了。

CPU 调度

CPU Scheduling 轨道是最常用且最重要的部分,它可视化了 Linux 内核调度器的决策过程。其数据来源于内核 ftrace 中的 sched/sched_switch 事件。

CPU 调度区域

每个 CPU 核心对应一行独立的轨迹。轨道上的不同色块,代表了在该时间片上,特定线程正在该 CPU 核心上运行。

  • UI 细节:点击 CPU 切片,详情面板会显示该次调度的 cpuend_statepriority、所属 process/thread 等;向下展开进程还能看到每个线程的独立轨道,便于跟踪单个线程的状态演化(参考 官方文档)。

image-20251112215540626

线程状态深度解析

理解 Linux 的线程状态是进行性能优化的前提。在 Perfetto 中,选中一个线程,下方的 Current State 面板会显示其当前状态。这些状态信息来源于 Perfetto 解析得到的 thread_state/thread_state_slice 表。

Running (绿色)

状态定义:绿色代表线程正在 CPU 上执行代码。这是唯一真正在消耗 CPU 资源进行计算的状态。

image-20251112215705017

分析要点

  • 执行时长:过长的 Running 状态,尤其是在关键线程上,通常意味着密集的计算任务,例如复杂的算法或循环。这会增加任务耗时,并可能阻塞其他线程的执行。
  • 运行核心:结合 CPU 的核心架构(例如 big.LITTLE)进行分析。一个计算密集型任务是否被调度到了预期的性能核心(大核)上,是评估调度策略是否合理的重要依据。
  • 运行频率:线程的实际执行速度也受 CPU 频率影响。即使线程运行在大核上,如果因为温控等原因导致降频,其性能表现也会下降。因此需要结合 CPU Frequency 轨道进行综合分析。

R (Runnable / 可运行)

状态定义:线程已具备所有运行条件,正在等待调度器分配 CPU 核心。在 Perfetto 的线程私有轨道中,Runnable 状态通常以浅绿色或白色条显示。

image-20251112220248837

分析要点

  • Runnable 与卡顿:对于 UI 线程这类对响应时间敏感的线程,长时间处于 Runnable 状态是造成卡顿的直接原因。它意味着线程无法及时获得 CPU 时间来处理任务(如 UI 绘制),从而导致掉帧。

Runnable 的三种类型:
仔细观察会发现,线程进入 Runnable 状态的“前身”各不相同。根据内核的调度时机,我们可以将其分为三种情况:

  1. **从睡眠中唤醒 (Wake-up)**:这是最常见的一种。线程因等待的资源(如锁、I/O、Binder 回复)已经就绪,从 SD 状态被唤醒,进入 Runnable 状态,等待被调度器选中执行。

  2. 用户抢占 (User Preemption):指线程的运行时间片用完,或出现更高优先级的任务,导致调度器在从内核态返回用户态时(如系统调用、中断返回后)决定换下当前线程。此时,被换下的线程从 Running 变为 Runnable。在底层的 sched_switch trace 中,它的 prev_state 标记为 R

  3. 内核抢占 (Kernel Preemption):指一个更高优先级的任务或中断,在当前线程正在执行内核态代码期间,就“强行”将其打断,使其让出 CPU。这种情况通常意味着一次更紧急的调度。此时,被换下的线程从 Running 变为 Runnable (Preempted)。在底层 trace 中,它的 prev_state 标记为 R+,Perfetto 据此进行了解析和展示。

理解这三种类型的区别,有助于更精细地判断调度延迟的原因。例如,大量的 Runnable (Preempted) 可能暗示着系统中存在频繁的、高优先级的唤醒源,导致关键线程在内核态中被频繁打断,或者当时的 CPU 已经满载了(比较常见的情况),这时候优先级比较低的 Task 就很容易被其他优先级高的 Task 抢占,被迫让出 CPU 。如果你的关键 Task 总是被抢占,那就需要调整优先级。

image-20251112220534899

S (Sleep / 可中断睡眠)

状态定义:线程因等待某个事件而进入睡眠,可以被信号中断。这是最常见的睡眠状态,通常情况下是良性的,因为它在等待期间不消耗 CPU 资源。

image-20251112221122170

分析要点

  • 等待的资源:如果关键线程(如 UI 线程)睡眠时间过长,同样会引发性能问题。常见的等待原因包括:
    • 锁竞争:等待获取一个 mutex (Java 锁或 native futex)。
    • Binder 通信:等待另一个进程通过 Binder 调用返回结果。
    • I/O 操作:等待网络 socket 数据 (epoll_wait)。
    • 显式休眠:代码中调用了 Thread.sleep()Object.wait()
  • 依赖分析:在 Perfetto 中,选中 CPU 区域被唤醒正在 Running 的 Task,就会有 UI 标识他是被哪个 CPU 上的哪个 Task 唤醒的,这有助于快速定位线程间的依赖关系。结合函数调用栈,可以进一步定位导致睡眠的具体代码。
    image-20251112221703122

D (Uninterruptible Sleep / 不可中断睡眠)

状态定义:线程在等待硬件 I/O 操作完成,期间不能被任何信号中断。此状态旨在保护进程与设备交互过程中的数据一致性。在 Perfetto 中,该状态通常显示为橙色或红色,是需要重点关注的信号。

image-20251112220738143

分析要点

  • 严重的性能瓶颈:长时间的 D 状态意味着线程被完全阻塞,无法响应任何事件。如果发生在 UI 线程,极易导致 ANR。
  • 常见原因
    1. 磁盘 I/O:频繁或单次大量的文件读写操作。
    2. 内存压力:系统物理内存不足,导致频繁的页面换入/换出 (swap),其本质是高频的磁盘 I/O。
    3. 内核驱动问题:部分内核驱动的实现缺陷也可能导致线程陷入 D 状态。
  • 排查方向:在 Current State 面板中,如果 D 状态伴有 (iowait) 标记,则明确表示在等待 I/O。需要检查应用的 I/O 模式,评估其合理性,例如是否将耗时 I/O 操作置于主线程,或是否存在可优化的空间(如减少 I/O 次数和数据量)。

唤醒关系分析

线程间的依赖关系是性能分析中的一个难点。一个线程长时间 Sleep,关键在于找出它在“等谁”。Perfetto 提供了强大的唤醒关系可视化功能。

  • UI 操作:在 Perfetto 的 CPU 区域中,用鼠标左键单击选中一个处于 Running 状态的线程 Task 。Perfetto 会自动绘制一条从“唤醒者”到“被唤醒者”的依赖箭头,并高亮显示唤醒源所在的线程切片。
    image-20251112221703122
  • 底层原理:该功能依赖于内核的 sched_wakeup ftrace 事件。当线程 T1 释放了某个资源(如解锁、完成 Binder 调用),而线程 T2 正在等待该资源时,内核会将 T2 标记为 Runnable 状态,并记录下 T1 -> T2 的这次唤醒事件。Perfetto 解析这些事件,从而构建出线程间的依赖链。

通过唤醒分析,可以清晰地追踪复杂的调用链,例如:UI 线程等待一个 Binder 调用 -> Binder 线程执行任务 -> Binder 线程等待另一个锁 -> 持锁线程释放锁并唤醒 Binder 线程 -> Binder 线程完成任务并唤醒 UI 线程。整个过程中的瓶颈点将一目了然。

调度唤醒与延迟分析

当线程 A 在 wait() 上挂起时,它进入 S(Sleeping)并从 CPU 运行队列移除。线程 B 调用 notify() 后,内核会把线程 A 转换为 R(Runnable)。此时线程 A“有资格”被放回某个 CPU 的运行队列,但这并不意味着“立刻”运行。

image-20251112222240991

常见等待原因包括:

  • 所有 CPU 都在忙:线程 A 需要等待空出队列槽位(或当前运行的线程优先级更高)。
  • 存在空闲 CPU,但迁移需时:负载均衡器将线程迁移到其他 CPU 需要一定时间窗口。

除非使用实时优先级,大多数 Linux 调度器配置并非严格的“work‑conserving”。调度器有时会“等待”当前 CPU 线程自然空闲,以避免跨 CPU 迁移带来的额外开销与功耗。这会在 R 状态下形成可观测的“排队延迟”。结合 sched_waking/sched_wakeup_new 可更精确地刻画“被唤醒”到“真正入队/运行”之间的时间段(参考官方文档:sched_waking / sched_wakeup 的差异与适用场景)。

  • 在目标线程的 thread_state 轨道中筛选 state=R 的切片,用作“调度延迟”的直接证据。
  • 同步对照同一 CPU 的其它重负载线程与 IRQ/SoftIRQ 轨迹,验证是否存在时间重叠的抢占或高优先级压制。
  • 若频繁在 R 状态排队并以 end_state=R+ 收尾,视为非自愿抢占严重,需评估优先级、放置与负载均衡策略。

sched_waking 与 sched_wakeup 的差异与非严格 work-conserving

  • sched_waking 在线程被标记为可运行(R)时就会发出,sched_wakeup 与跨 CPU 唤醒有关,可能记录在源或目的 CPU 上;对大多数延迟分析而言,仅 sched_waking 已足够(参见官方说明)。
  • 多数 Linux 调度配置在通用优先级下并非严格“work-conserving”。调度器有时会“等待当前 CPU 空闲”以避免跨核迁移带来的额外开销与功耗,这会造成 R 状态下的等待时间(排队延迟)并非异常,而是权衡后的结果(参见 Perfetto CPU Scheduling)。

用户态与内核态:sys_* 切片定位空火焰图

  • Running 的绿色切片未必都是应用代码在忙。若线程陷入单个长系统调用如 sys_readsys_futex,用户态采样火焰图可能几乎为空。
  • 若 UI 线程某段 sched_slice 很长,而 CPU 火焰图热点很少或几乎没有:
    1. 打开该线程的 slice 视图,查找是否存在长时间 sys_* 切片;
    2. 若存在:瓶颈多在 I/O 或同步原语,优先检查 I/O 路径、锁粒度与访问模式;
    3. 若不存在:回到火焰图,继续剖析用户态热点函数。
  • 建议:合并 I/O、切换异步、优化锁竞争、降低系统调用频率与单次数据量。

CPU 时间与墙上时间

image-20251112223105996

  • Wall 是切片从开始到结束的真实世界时间,CPU 是真正在 CPU 上运行的时间。
  • 二者关系:Wall = CPU + Runnable + Sleep
    1. 选中目标切片(如 Choreographer#doFrame),比较 WallCPU
    2. Wall ≈ CPU,则为计算过重,使用火焰图定位热点;\n 3) 若 Wall >> CPU,则为调度或依赖等待,查看 thread_stateR/S/D 分布与唤醒链。

CPU Frequency (CPU 频率) 深度解析

CPU 频率直接影响代码执行速度,同时也与功耗正相关。CPU Frequency 轨道显示了每个核心在特定时间的运行频率(scaling_cur_freq)。但更重要的是理解其背后的限制因素:

影响 CPU 频率的核心因素

  1. 任务负载 (Task Utilization): 这是最主要的驱动因素。现代 Android 系统多采用 schedutil 作为 cpufreq 调频策略。它直接关联调度器(Scheduler),根据线程的“繁忙”程度(utilization)来决定频率。一个高负载的线程(util 很高)会促使 schedutil 请求更高的频率,反之亦然。

  2. 场景策略 (Power HAL): Android Framework 通过 Power HAL 向底层传递当前的系统状态,即“场景”。例如,在应用启动、游戏、或触摸屏幕时,Power HAL 会向内核请求更高的性能,这通常通过抬高 CPU 的地板频 (Floor / scaling_min_freq) 和/或 天花板频 (Ceiling / scaling_max_freq) 来实现,确保 CPU 能快速响应。

  3. 温度控制 (Thermal Throttling): 这是拥有最高优先级的限制。当设备温度(来自电池、CPU、NPU 等传感器)超过预设阈值时,温控系统会强制降低 CPU 的天花板频,以减少发热,保护硬件。此时,即使有高负载任务,CPU 频率也无法提升,是导致游戏掉帧、应用卡顿的常见外部原因。

  4. 功耗限制与省电模式: 在低电量或开启省电模式时,系统同样会通过降低天花板频、限制最高性能输出来延长续航。

因此,当发现一个重任务在运行时 CPU 频率上不去,不仅要看当前频率,更要关注 scaling_max_freq 是否被限制了。这通常意味着性能瓶颈的根源不在于应用代码本身,而在于系统的温控或功耗策略。

image-20251112223237018

上图是 CPU 频率区域的标识图,鼠标放上去就可以看到当前的频率,CPU 频率变化很快。图中 CPU 有带颜色和不带颜色的部分,带颜色标识当前 CPU 有 Task 在跑,不带颜色说明当前 CPU 是空的,无 Task。

频率数据采集与平台差异

  • 两种获取方式:
    • 事件驱动:启用 power/cpu_frequency,在内核 cpufreq 驱动变更频率时记录事件。并非所有平台支持,在多数 ARM SoC 上可靠,在大量现代 Intel 平台上常无数据。
    • 轮询采样:启用 linux.sys_stats 并设置 cpufreq_period_ms > 0,定期读取 /sys/devices/system/cpu/cpu*/cpufreq/cpuinfo_cur_freq,ARM/Intel 均可用。建议与事件驱动组合使用以补齐“初始频率快照”。
  • Android 设备常以“簇”为单位调频,常见现象是同簇内多个 CPU 同步变频。
  • 已知问题:
    • 事件仅在频率变化时产生,短 Trace 或稳定场景可能出现左侧“空白”。此时需依赖轮询补足。
    • 某些 UI 版本在未捕获 Idle 状态时不渲染 cpufreq 轨道,但数据仍可通过查询得到。
    • 参考:CPU frequency and idle states 官方说明。

big.LITTLE:相同频率不等于相同性能或能耗

  • 在异构 CPU 上,同为 2.0GHz,小核与大核的实际算力和能耗并不等价。频率必须结合核心类型理解。
  • 核心容量(capacity)与 IPC:大核通常具备更宽的乱序执行、更多执行端口、更大的缓存与更激进的预取/分支预测,单位周期完成的指令数(IPC)更高;同频下,大核完成同样工作所需时间更短、能量更少。
  • 簇级 DVFS(cluster-level DVFS):移动 SoC 多以“簇”为单位调频。小核簇的高频并不等同于大核簇的中频性能输出;不同簇的电压-频率-能量曲线不同,同一频点无法横向类比“每瓦性能”。
  • 内存/缓存/互联瓶颈:热点若受制于内存带宽、LLC 命中率或系统互联(NoC),单纯提升小核频率收益有限;大核更大的缓存/更强预取可显著降低同任务的访存等待,体现“同频不同效”。
  • 能效曲线非线性:小核在接近最高频时常进入能效陡降区(电压抬升使边际能耗激增),而大核在中等频点可能达到“更高单位能效”。同频比较忽略了“电压-频率-能量”的三维权衡。

P-States 与 governor

  • CPU 频率并非连续值,而是离散的性能状态 P-States。常用 governor 为 schedutil,依据任务利用率等信号选择合适 P-State,并联动电压调节。
  • UI 上的频率变化,实质是 governor 在不同 P-State 之间切换。高频不必然更快或更省电,需要结合核簇类型与当时的上限/下限约束理解。
  • 核验步骤:
    1. CPU Frequency 观察频率的上下限是否被拉高/压低(如场景策略/温控限制 scaling_max_freq)。
    2. CPU Scheduling 查看关键线程运行在哪类核心(结合设备的核编号划分)。
    3. 若“高频 + 小核 + 仍慢”,优先考虑选核/访存瓶颈,而非简单“频率不够”。
  • 建议结合 Power HAL 场景、热管理与任务画像(CPU 密集或访存密集)综合优化,避免单纯以频率作为调参目标。参考官方文档: Perfetto CPU Scheduling

Linux 内核调度策略:选核与迁移

理解了线程状态和频率后,我们还需要深入了解 Linux 内核调度器(在 Android 中主要是 EAS - Energy Aware Scheduling)的两个核心行为:如何为任务挑选一个 CPU 核心,以及为何要将任务从一个核心迁移到另一个。

选核逻辑 (Task Placement)

当一个线程从 Sleep 状态被唤醒,或一个新线程被创建时,EAS 调度器需要为它选择一个最合适的 CPU 核心。其核心目标是在满足任务性能需求的前提下,尽可能地降低系统功耗。决策流程大致如下:

  1. 评估任务负载 (Task Utilization): 调度器会评估该线程的 util,即它需要多少计算资源。这是一个动态调整的值,反映了线程历史上的繁忙程度。
  2. 寻找“足够”的核心: 调度器会遍历所有可用的 CPU 核心,比较任务的 util 和每个核心的 capacity(容量/最大计算能力)。大核的 capacity 远高于小核。调度器会寻找 capacity > util 的核心,即能“装下”这个任务的核心。
  3. 寻找“最节能”的核心: 在所有满足 capacity 要求的核心中,调度器会利用预置在内核中的“能量模型”(Energy Model)进行计算。这个模型知道每个核心在每个频率下运行的功耗。调度器会选择一个能让整个系统(包括任务本身和其他正在运行的任务)总功耗最低的 CPU 核心作为最终选择。

简而言之:EAS 的目标不是为任务找一个最快的核,而是找一个“刚刚好够用”且“最省电”的核。

核心迁移逻辑 (Task Migration)

将任务从一个 CPU 核心移动到另一个,是调度器进行动态调优的关键手段。主要发生在以下情况:

  1. 负载均衡 (Load Balancing): 这是最常见的迁移原因。调度器会周期性地检查系统是否处于“负载不均衡”状态。例如,CPU-1(小核)上挤满了高负载任务导致其利用率饱和,而 CPU-7(大核)却很空闲。此时,调度器会判定系统失衡,并将 CPU-1 上的某个高负载任务“拉到”(pull)CPU-7 上运行,以恢复平衡,提升性能。

  2. 唤醒时迁移 (Wake-up Migration): 当一个线程睡醒时,调度器会重新评估它的最佳核心。如果这个线程在睡眠期间 util 发生了变化(例如,一个后台下载线程突然收到了一个大文件下载任务,util 飙升),或者原先运行的核心现在非常繁忙,调度器就可能在唤醒时直接为它选择一个更合适的新核心,而不是让它在原来的地方排队。

理解选核与迁移逻辑,有助于我们判断调度器的行为是否“异常”。例如,一个明显的前台 UI 线程被不合理地长时间限制在小核上,或者在大小核之间过于频繁地“反复横跳”,都可能暗示着系统调度策略或任务优先级设置存在问题。

当然目前 Android 厂商针对调度器有做非常多的客制化,关键 Task(Critical Task)通常会占用更多的 CPU ,甚至更容易上大核。另外还有很多绑核策略、签核策略,导致大家看到的每个厂商的现象都不一样。这也是各个厂商的核心竞争力(比如 Oppo 的蜂鸟引擎)。

实战与 SQL

使用 SQL 进行量化分析

Perfetto 内置的 SQL 查询引擎是其强大功能之一,允许开发者对 Trace 数据进行精确的聚合、筛选和分析。以下是一些常用的 CPU 分析查询。

1. 计算各进程的 CPU 总时长

此查询统计每个进程在所有 CPU 上运行的总时长,按降序排列,用于快速定位消耗 CPU 资源最多的进程。

1
2
3
4
5
6
7
8
SELECT
process.name,
sum(dur) / 1e9 AS total_cpu_time_s
FROM sched
JOIN thread ON sched.utid = thread.utid
JOIN process ON thread.upid = process.upid
GROUP BY process.name
ORDER BY total_cpu_time_s DESC;

2. 分析单个线程的时间状态分布

此查询基于 thread_state 表,可用于分析特定线程(示例为 surfaceflinger)在各种状态下的时间分布,从而判断其主要瓶颈。

1
2
3
4
5
6
7
8
9
10
11
12
13
SELECT
CASE state
WHEN 'Running' THEN 'Running'
WHEN 'R' THEN 'Runnable'
WHEN 'S' THEN 'Interruptible Sleep'
WHEN 'D' THEN 'Uninterruptible Sleep'
ELSE state
END AS human_state,
sum(dur) / 1e6 AS total_time_ms
FROM thread_state
WHERE utid = (SELECT utid FROM thread WHERE name = 'surfaceflinger' LIMIT 1)
GROUP BY human_state
ORDER BY total_time_ms DESC;

3. 查找特定时间段内 CPU 消耗最高的线程

此查询用于分析特定场景(如应用启动的 2-5s 时间段)中,消耗 CPU 时间最长的线程。

1
2
3
4
5
6
7
8
9
SELECT
thread.name,
sum(dur) / 1e9 AS cpu_time_s
FROM sched
JOIN thread ON sched.utid = thread.utid
-- 时间戳单位为纳秒,可以自己加上 WHERE ts > 2e9 AND ts < 5e9 来取某一段时间
GROUP BY thread.name
ORDER BY cpu_time_s DESC
LIMIT 20;

4. 查看线程在各 CPU 核心上的运行时间分布

此查询有助于了解一个线程的 CPU 亲和性,以及它是否在预期的大小核上运行。

1
2
3
4
5
6
7
SELECT
cpu,
sum(dur) / 1e6 AS time_on_cpu_ms
FROM sched
WHERE utid = (SELECT utid FROM thread WHERE name = 'system_server' LIMIT 1)
GROUP BY cpu
ORDER BY cpu;

5. 计算各进程的 CPU 利用率

此查询计算了每个进程在整个 Trace 时间范围内的 CPU 利用率。

1
2
3
4
5
6
7
8
SELECT
process.name AS process_name,
100 * sum(dur) / CAST(TRACE_END() - TRACE_START() AS REAL) AS cpu_utilization_percent
FROM sched
JOIN thread ON thread.utid = sched.utid
JOIN process ON process.upid = thread.upid
GROUP BY process.name
ORDER BY cpu_utilization_percent DESC;

总结

对 Perfetto 中 CPU 信息的熟练分析,是 Android 性能优化的关键技能。通过深度理解 核心架构线程状态唤醒关系频率限制C-State,并结合强大的 SQL 查询进行量化分析,开发者可以精准地定位并解决各类性能与功耗问题。

关于我 && 博客

下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!

  1. 博主个人介绍 :里面有个人的微信和微信群链接。
  2. 本博客内容导航 :个人博客内容的一个导航。
  3. 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
  4. Android性能优化知识星球 : 欢迎加入,多谢支持~

一个人可以走的更快 , 一群人可以走的更远

微信扫一扫

CATALOG
  1. 1. 本文目录
  2. 2. Perfetto 系列文章
  3. 3. Perfetto 中的 CPU 信息概览
  4. 4. 抓取 CPU 信息所需要的 Trace Config
  5. 5. CPU 核心架构:big.LITTLE
  6. 6. CPU 调度
    1. 6.1. 线程状态深度解析
      1. 6.1.1. Running (绿色)
      2. 6.1.2. R (Runnable / 可运行)
      3. 6.1.3. S (Sleep / 可中断睡眠)
      4. 6.1.4. D (Uninterruptible Sleep / 不可中断睡眠)
    2. 6.2. 唤醒关系分析
      1. 6.2.1. 调度唤醒与延迟分析
      2. 6.2.2. sched_waking 与 sched_wakeup 的差异与非严格 work-conserving
    3. 6.3. 用户态与内核态:sys_* 切片定位空火焰图
    4. 6.4. CPU 时间与墙上时间
  7. 7. CPU Frequency (CPU 频率) 深度解析
    1. 7.1. 影响 CPU 频率的核心因素
    2. 7.2. 频率数据采集与平台差异
    3. 7.3. big.LITTLE:相同频率不等于相同性能或能耗
      1. 7.3.1. P-States 与 governor
  8. 8. Linux 内核调度策略:选核与迁移
    1. 8.1. 选核逻辑 (Task Placement)
    2. 8.2. 核心迁移逻辑 (Task Migration)
  9. 9. 实战与 SQL
    1. 9.1. 使用 SQL 进行量化分析
    2. 9.2. 1. 计算各进程的 CPU 总时长
    3. 9.3. 2. 分析单个线程的时间状态分布
    4. 9.4. 3. 查找特定时间段内 CPU 消耗最高的线程
    5. 9.5. 4. 查看线程在各 CPU 核心上的运行时间分布
    6. 9.6. 5. 计算各进程的 CPU 利用率
  10. 10. 总结
  • 关于我 && 博客