EN

Android Perfetto 系列 9:CPU 信息解读

字数统计: 7.9k阅读时长: 29 min
2025/11/12
loading

App 主线程没有按时处理一帧时,先分清它在 Running、Runnable 还是 Sleep;这三种状态对应的下一步调查完全不同。这篇用 Perfetto 的 CPU 调度、频率、空闲状态和采样数据,把「CPU 慢」拆成可核对的现象。

文中的核编号、频率和调度策略都要按目标设备确认。同一条线程运行在小核上,不足以单独证明调度错误。

版本说明(2026-10 核查):保留原案例图,图中设备、核编号和厂商策略不能推广到所有 Android 手机。表结构与 UI 实现按 Perfetto v58.3 核对;调度与调频机制参照 Linux v6.12,不把它当成截图设备的内核版本。Performance Hint 另标 API 31 起的接口边界。

本文目录

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 系列 9:CPU 信息解读
  11. Android Perfetto 系列 10:Binder 调度与锁竞争
  12. Android Perfetto 系列 11:PerfettoSQL、Trace Processor 与回归检测
  13. Android Perfetto 系列 12:Trace 数据流与丢失排查
  14. Android Perfetto 系列 13:Perfetto SDK、Track Event 与 App 现场 Trace
  15. Android Perfetto 系列 14:heapprofd 与内存 Profiling
  16. Android Perfetto 系列 15:Boot Trace、长时间现场 Trace 与偶发问题抓取
  17. Android Perfetto 系列 16:GPU、Power Counters 与硬件瓶颈分析
  18. Android Perfetto 系列 17:专项场景自动化、问题清单与平台体系
  19. Android Perfetto 系列 18:响应速度实战,从输入事件到首帧反馈
  20. 视频(B站) - Android Perfetto 基础和案例分享
  21. 视频(B站) - Android Perfetto 分享 - 出图类型分享:AOSP、WebView、Flutter + OEM 系统优化分享

Perfetto 中的 CPU 信息概览

在 Perfetto UI 中,CPU 相关的信息通常分组置于顶部,是性能分析的起点。通常要同时看以下三类信息,频率与 idle 也可能叠在同一轨道:

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

image-20251112214415666

把 CPU 轨道和目标线程放在同一时间窗口,可以先回答:

  • 应用主线程为何没有执行?是否被其他线程抢占?
  • 某个任务执行缓慢的原因是什么?是否被调度到了低性能核心?
  • 在特定场景下,CPU 频率是否受限?
  • 应用转后台后 CPU 是否进入 idle?整机是否 suspend 需另看 suspend 事件。

抓取 CPU 信息所需要的 Trace Config

下面是 CPU 调度、唤醒和频率采集的 textproto 数据源片段,参考 Perfetto Android 分析示例。它不是 YAML,也不是完整录制配置;需要与 buffers、duration_ms 等录制设置合并。事件和符号权限取决于设备内核、构建与 Perfetto 版本。

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
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: "power/cpu_frequency"
ftrace_events: "power/cpu_idle"
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
}
}
}

这个片段不包含 syscall、CPU callstack sampling 或厂商的 Min/Max Frequency Limit 事件。第一张图出现这些额外轨道,不代表用此片段就能全部录到。cpufreq_period_ms: 250 是轮询间隔,不会捕捉两次读取之间的每次变频;与频率变化事件结合使用。

CPU 核心架构:big.LITTLE

分析调度之前,先确认设备拓扑。手机 SoC 可以使用不同容量的核簇,但不一定都有传统的 LITTLE 微架构;“小核”“大核”有时只是同一芯片内的相对称呼。不能从核心编号、频率上限或截图里的标注直接推导型号。

  • 小核(LITTLE):传统异构设计中偏向低功耗的核心;不代表后台线程只能在这里运行。
  • 大核(big):偏向更高性能,适合较重负载;性能与能耗仍取决于具体任务。
  • 超大核(Prime):部分芯片的最高性能核心或核心组,不保证只有一个。

Perfetto 的 CPU 编号通常从 0 开始,编号不等于性能排序。以下三图分别保留原有的 4+4、4+3+1、5+2 分组标注,属于特定截图示意,不能据此认定具体芯片或套用到另一设备。

image-20251112214626810

image-20251112215105550

image-20251112214833158

从设备规格及 /sys/devices/system/cpu/ 的 topology、capacity、cpufreq policy 信息核对。policy 分组说明共享调频控制,也不必等同微架构分组。

CPU 调度

CPU Scheduling 轨道是最常用且最重要的部分,它展示 Linux 内核调度器产生的运行区间,不直接记录完整的选核决策。其数据来源于内核 ftrace 中的 sched/sched_switch 事件。

CPU 调度区域

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

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

image-20251112215540626

线程状态深度解析

理解 Linux 的线程状态是进行性能优化的前提。在 Perfetto 中,选中线程状态切片,可在详情面板读该区间的状态;面板名称随 UI 版本变化。这些状态信息来源于 Perfetto 解析得到的 thread_state 表。

Running (绿色)

状态定义:绿色代表线程正在 CPU 上执行代码。这是从调度事件重建的在核运行区间,可能包含用户态、内核态、忙等和 CPU stall;还可能覆盖未单独扣除的中断处理。

image-20251112215705017

分析要点:

  • 执行时长:长时间 Running 说明线程占用了 CPU,但可能是计算、系统调用或忙等。下一步要用采样或代码埋点定位具体工作。
  • 运行核心:结合 CPU 的核心架构(例如 big.LITTLE)进行分析。一个计算密集型任务是否被调度到了预期的性能核心(大核)上,是评估调度策略是否合理的重要依据。
  • 运行频率:线程的执行速度也可能受频率限制影响;影响大小取决于计算、访存和等待的比例。因此需要结合 CPU Frequency 轨道进行综合分析。

R (Runnable / 可运行)

状态定义:线程已可运行,但尚未在 CPU 上执行;它仍受 affinity、cpuset、调度类及带宽限制等约束。在 Perfetto 的线程私有轨道中,Runnable 状态通常以浅绿色或白色条显示。

image-20251112220248837

分析要点:

  • Runnable 与卡顿:UI 线程长时间处于 Runnable,说明已经可以运行却尚未拿到 CPU。若它与晚帧时间窗口重合,才进一步判断这段排队对帧截止时间的影响。

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

  1. 从睡眠中唤醒-Wake-up:这是最常见的一种。线程在相应等待条件满足时从 S 或 D 唤醒;可中断等待还可能因信号结束,带超时的等待也可能到期。之后进入 Runnable 状态,等待被调度器选中执行。

  2. 运行后仍可运行:线程从 Running 被换下后保持 Runnable,sched_switch 中的 prev_state 可记为 R。单看这个状态不足以还原具体调度原因。

  3. 被抢占:prev_state 为 R+ 时,Perfetto 展示为 Runnable (Preempted)。这说明线程被换下时仍可运行,不能据此断定一定发生在内核态,也不能直接归因于某个高优先级线程。

出现大量 Runnable (Preempted) 时,对齐同一 CPU 上接着运行的线程、优先级和系统负载,才知道是竞争、时间片还是其他调度条件。改线程优先级之前,还要确认它是否属于用户可感知的关键路径。

image-20251112220534899

S (Sleep / 可中断睡眠)

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

image-20251112221122170

分析要点:

  • 等待的资源:如果关键线程(如 UI 线程)在待完成工作的关键路径上睡眠过久,才需分析是否影响响应。空闲等待本身不是性能问题。常见的等待原因包括:
    • 锁竞争:等待锁或条件变量,底层可能使用 futex;Java monitor、native mutex 与 futex 不是同一种抽象。
    • Binder 通信:等待另一个进程通过 Binder 调用返回结果。
    • I/O 操作:等待 fd 就绪,例如 epoll_wait;也可能是 Looper 空闲等待,并非必然在等网络。
    • 显式休眠:代码中调用了 Thread.sleep() 或 Object.wait()。
  • 依赖分析:在 Perfetto 中,相关唤醒事件可用时,选中后续 CPU slice 可查看唤醒来源,这有助于快速定位线程间的依赖关系。结合函数调用栈,可以进一步定位导致睡眠的具体代码。
    image-20251112221703122

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

状态定义:D 表示不可中断睡眠,常见于内核等待路径,但不只对应硬件 I/O。Perfetto 的颜色会随轨道和主题变化,排查时以状态字段、io_wait 和 blocked_function 为准。

image-20251112220738143

分析要点:

  • 关键路径上的等待:D 等待仍可由等待条件完成而唤醒,不是“无法响应任何事件”。若阻塞关键路径且超过相应超时,可能参与 ANR;图中约 103µs 的一次等待不能证明 ANR。
  • 常见原因:
    1. 磁盘 I/O:频繁或单次大量的文件读写操作。
    2. 内存压力:回收或换页可能延长内核等待;是否涉及存储 I/O 要继续查具体事件和设备实现。
    3. 内核驱动问题:部分内核驱动的实现缺陷也可能导致线程陷入 D 状态。
  • 排查方向:io_wait 标记是重要线索,但不能单独给出文件或设备原因。结合 blocked_function、I/O 事件和目标线程调用栈继续定位。

唤醒关系分析

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

  • UI 操作:在 Perfetto 的 CPU 区域中,用鼠标左键单击选中一个处于 Running 状态的线程 Task 。在采到可关联的唤醒事件时,Perfetto 可以绘制一条从“唤醒者”到“被唤醒者”的依赖箭头,并高亮显示唤醒源所在的线程切片。
    image-20251112221703122
  • 底层原理:Perfetto v58.3 的 ParseSchedWaking 用 sched_waking 的当前执行上下文和目标 pid 记录唤醒关系。中断上下文、缺事件和 trace 边界会影响归因;被中断时的当前线程不一定是业务上完成资源的线程。

唤醒关系能指出一个线程何时让另一个线程变为 Runnable。Binder 调用、锁等待和资源归属还需要各自的 trace 事件或调用栈核对;一条唤醒箭头不足以还原完整调用链。

调度唤醒与延迟分析

线程被唤醒后,仍可能等待一段时间才开始执行。Java Object.notify() 与内核 wakeup 不能逐次等同:等待者还要重新取得 monitor,可能再次阻塞,也可能因超时或伪唤醒而返回。

image-20251112222240991

图中 UMSDecisionSing 的唤醒来源是 CPU6 上的 kworker,之后在 CPU3 运行,标出的约 1.463ms 是唤醒到开始执行的延迟。它不能单独证明是优先级、迁核还是带宽限制造成的。

  • 在 thread_state 中同时统计 R 和 R+,再对齐实际开始执行的 CPU。
  • 检查允许的 CPU 集合、同核竞争、调度类、cgroup 配额和 IRQ/SoftIRQ。系统还有空闲 CPU,不代表该线程可以在那里立即运行。
  • sched.end_state=R+ 描述上一段 Running 为什么结束;不是 Runnable 切片自身的结束字段。

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

按 Linux v6.12 的 tracepoint 定义,sched_waking 在发起唤醒的上下文中记录;sched_wakeup 在目标任务实际置为 TASK_RUNNING 时记录,未必仍在同一上下文。后者不只用于跨 CPU 唤醒,也不表示目标线程已经开始执行。sched_wakeup_new 则用于新任务。

Perfetto 调度文档说明,普通调度配置可能为放置和能耗权衡留下排队时间。不能反过来把任何“有空核但仍 Runnable”都归因于节能;先排除 CPU 集合、配额和采集缺失。需要细分唤醒路径时,再结合两类事件及跨核通知,而不是把 sched_waking 当作已经完成入队。

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

sys_* 切片需要 raw_syscalls/sys_enter 与 raw_syscalls/sys_exit;前面的配置没有启用它们。官方 syscall 文档另注明 Android userdebug 构建限制。采集权限和内核支持也要确认。

长系统调用的墙上时间可能包含 Running、Runnable 和 Sleep,不能全部算成内核 CPU 时间。用户态火焰图稀疏时,先查采样是否启用、scope/权限是否覆盖目标、样本数、丢失及 unwinding 错误,再查对应窗口的 syscall 和线程状态。

  • 长 syscall 与 Sleep/D 重合:沿具体等待对象查 I/O 或同步依赖。
  • syscall 内主要 Running:结合内核采样和调用栈查实际工作。
  • 没有 syscall slice:可能没有采集,不能据此认定只剩用户态热点。

CPU 时间与墙上时间

image-20251112223105996

  • Wall 是区间的经过时间;这里的 CPU 指该线程的 Running 时间,不包含它等待期间其他线程做的工作。
  • 在线程状态完整、无重叠且按窗口裁剪时,Wall 可分解为 Running、Runnable(含 R+)及各睡眠/阻塞等状态。缺失区间要单列为未知,不能补成零。
  • 图中选中的是 RenderThread 的 DrawFrames -1:Wall 约 127.411ms,Running 约 95.485ms,其余包括 S、D、R、R+。这是图中的记录,不能当作每帧预算。
  • 对主线程 Choreographer#doFrame 也可这样分析。Wall≈CPU 表示主要在核上,不唯一等于算法计算;Wall≫CPU 则继续分辨调度排队与依赖等待。

CPU Frequency (CPU 频率) 深度解析

CPU 频率会影响执行时间与功耗,但频率轨道不是功率计。Linux v6.12 cpufreq 文档说明,scaling_cur_freq 多数情况下反映驱动最近请求的频率,未必是硬件瞬时有效频率;ftrace 的频率事件也受驱动上报能力限制。

影响 CPU 频率的核心因素

  1. 任务负载:使用 schedutil 的系统会参考调度器的利用率信号请求频率;它不是所有 Android 厂商的统一 governor,需读目标 policy 的 scaling_governor。请求还受限频、速率限制和硬件响应影响。
  2. 场景策略(Power HAL):启动、交互等提示由设备实现解释,可能调整频率约束、任务放置或其他参数,不能一律等同抬高 scaling_min_freq/scaling_max_freq。
  3. 温控:热管理可能限制 CPU/GPU 性能,但实现不一定只改 cpufreq 上限。要有温度、限频或 thermal 状态证据,不能从“频率上不去”单独归因温控。
  4. 电源与省电策略:电量、供电能力和系统模式也可能限制性能,具体措施依设备而定。

重任务频率偏低时,一并检查运行核、负载、频率 policy、热状态和工作类型;系统约束与应用低效可以同时存在。

image-20251112223237018

图中鼠标读数 2,100,000 kHz 是 2.1GHz,左侧 5GHz 是纵轴刻度。频率轨道的彩色/灰色叠加表达已采集的非 idle/idle 状态,不是通过颜色直接数 Task。第一张总览另有 Min/Max Freq Limit 轨道,它们与当前频率不是同一数据。

频率数据采集与平台差异

Perfetto 频率与 idle 文档给出两种采集路径:

  • power/cpu_frequency:记录驱动上报的变化,平台未发事件时没有数据。事件前的未知频率不能自动填成零。
  • linux.sys_stats 的 cpufreq_period_ms:轮询可读的 sysfs 值,为事件流补充快照,但不能恢复两次采样之间的变化。路径不存在、权限不足或核离线时不能保证完整覆盖。

共享 cpufreq policy 的 CPU 常同时变频,但 policy 与“小/中/大”标签不必一一对应。Perfetto v58.3 的 CPU Frequency 插件通过 LEFT JOIN 关联可选 idle 轨道,缺 idle 不会自动取消频率轨道。

补充 CPU Idle 的读法:power/cpu_idle 原始 counter 中 4294967295(0xffffffff)表示退出 idle;0 可以是第一个 idle 状态,不能把原始数值 0 当成“正在运行”。状态编号与唤醒延迟依 SoC 定义,CPU idle 不等于整机 suspend。空闲时图上的频率可能只是进入 idle 前最后一次上报值,不能用它直接计算耗电。

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

  • 在异构 CPU 上,同为 2.0GHz,小核与大核的实际算力和能耗并不等价。频率必须结合核心类型理解。
  • 核心容量(capacity)与 IPC:大核通常能在同频下更快完成计算,但不保证同一任务总能耗更低;还要计入电压、工作时间和空闲状态。
  • 簇级 DVFS(cluster-level DVFS):移动 SoC 多以“簇”为单位调频。小核簇的高频并不等同于大核簇的中频性能输出;不同簇的电压-频率-能量曲线不同,同一频点无法横向类比“每瓦性能”。
  • 内存/缓存/互联瓶颈:热点若受制于内存带宽、LLC 命中率或系统互联(NoC),单纯提升小核频率收益有限;更大的缓存或更强的预取有时能缓解等待,但效果依访问模式与共享瓶颈而定。
  • 能效曲线非线性:小核在接近最高频时常进入能效陡降区(电压抬升使边际能耗激增),而大核在中等频点可能达到“更高单位能效”。同频比较忽略了“电压-频率-能量”的三维权衡。

P-States 与 governor

P-State 是性能状态的概念;具体驱动可能提供离散频率表,也可能接受性能范围并由硬件自主调节,不能把每次图形变化都解释成 schedutil 切换一个离散 P-State。

  1. 在 CPU Frequency 查看已上报频率;要判断上限/下限,另查 policy 或已采到的 limit 轨道,不能从当前频率曲线推出上限。
  2. 在 CPU Scheduling 查看关键线程实际运行核,结合已核实的拓扑。
  3. “高频 + 较小核心 + 仍慢”只是线索:继续看执行内容、缓存/内存访问和调度约束,再决定优化代码还是系统策略。

需要比较能效时,使用相同工作量、热状态和可用的功率/能量测量,不能按 GHz 高低替代测量。

ADPF / Performance Hint 应该怎么看

PerformanceHintManager 从 Android 12 / API 31 提供。Session 面向一组相关线程,报告目标工作时长和实际工作时长,由系统尝试调整放置或性能;设备不支持时创建可能返回 null。它不保证提频,也不是锁定大核的 API。

  1. 先确定周期工作及其预算。刷新间隔 16.6/11.1/8.3ms 不自动等于 App 可用的全部工作时长,还要考虑流水线与预留时间。
  2. 用同一窗口的 Running、Runnable、等待和采样判断瓶颈。提示可能影响调度,但不能消除锁依赖或替代代码优化。
  3. 对比实际工作时长、核放置和频率,并控制场景、热状态和后台负载;一次 API 调用或一次迁核不能证明 hint 的效果。
  4. 继续检查热/功率限制。提示不构成绕过这些约束的保证。

目标时长应反映这组线程本轮工作的预算;不要把整帧周期或上一轮耗时不加区分地填进去。

CPU Profiling、火焰图与符号化

CPU 状态回答线程是否拿到 CPU;on-CPU sampling 则帮助判断它在运行时执行了什么。先确定问题窗口与目标线程,再看 Running 占比、样本和调用栈。如果目标主要在 R/S/D,也可采样竞争者或依赖线程,但只看目标线程的 on-CPU 火焰图无法解释整个等待链。

Perfetto v58.3 的 CPU profiling 指南使用 linux.perf,需启用 callstack_sampling 才采调用栈,前面的 CPU Config 不包含它。该版本指南的 Android 录制流程要求 Android 15+;user 构建还要求目标 App 可 profile/debug,不能据此断言所有旧版本均支持或均不支持。

方式 适合场景 注意点
Perfetto linux.perf 同时间轴关联调度与调用栈 检查采样事件、scope、权限、开销和丢失
simpleperf App/native 热点与符号化 保存 perf.data、匹配的二进制及符号
Gecko Profile JSON 从 simpleperf 导出到 Perfetto UI 或 Firefox Profiler 转换保留的时间与权重口径需核对

Android 16 simpleperf 脚本文档给出 gecko_profile_generator.py -i perf.data 导出路径;不要把它笼统叫作“生成 Perfetto protobuf”。binary_cache_builder.py -lib 可从匹配的符号目录收集二进制,native build ID、ART/JIT 信息和设备系统库版本仍需对应。

采样不是逐函数计时。以按 CPU 时间约 100Hz 的采样作量级估算,单线程持续运行 10s 约对应 1000 个机会,80ms 只有约 8 个;这不是硬性上限或保证,实际数量受采样事件、频率调节、迁核和丢失影响。按硬件事件周期采样更不能直接按墙上秒数乘频率。

  • self:栈叶节点自身的样本数或权重,不含子调用。
  • cumulative:该节点及其子树的样本数或权重。不同节点可能包含同一批样本,不能逐行相加当总量。

只有合适的时间采样与权重才能近似解释成 CPU 时间占比。符号化也是容易踩坑的一步:unknown 一多,先别急着优化代码,看看是缺符号、栈展开失败,还是采集范围不足。未知部分不能直接算给某个函数。

下面是报告格式示意,不是本文截图的实测结果:

1
2
3
4
5
6
7
8
窗口:12.300s - 12.860s
线程:RenderThread(另记录对应 utid)
Wall:560ms
Running:492ms;Runnable(含 R+):18ms
其余已知等待状态:50ms;未知覆盖:0ms
采样:按 CPU 时间约 100Hz,观测到 51 samples
Top cumulative:某 Skia 路径 34 samples
边界:先确认符号与采样权重;样本少,不做精细占比推断

回到时间轴核对热点是否落在用户感知窗口。不要把整段录制的火焰图直接当成某个晚帧的原因。

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

EAS(Energy Aware Scheduling)参与满足条件的 fair-class 任务放置,不是 Android 的全部调度器,也不替代实时调度类或每个 CPU 上的任务选择。下面按 Linux v6.12 EAS 文档说明一般思路;厂商内核、利用率跟踪与策略可能不同。

选核逻辑 (Task Placement)

启用 EAS 且适用时,唤醒选核会结合任务历史利用率、允许的 CPU 集合、CPU 容量与当前负载,并利用 Energy Model 估算不同放置的能量成本。

  1. 评估负载:利用率来自运行历史及估计,不是调度器预先知道下一轮要做多少工作。
  2. 筛选候选:考虑容量与剩余空间,并受 affinity/cpuset 等限制。不是遍历所有物理核后简单判断 capacity > util。
  3. 比较能量估计:模型比较候选放置对 performance domain 的影响;它是估计,不是实时测得的整机功率,也不保证全局最优。

EAS 通常更适合轻中负载。在 root domain 进入 overutilized 状态时,相关路径会转向负载均衡,不能把高负载下的所有迁移都解释成 EAS 选了“最省电”的核。

核心迁移逻辑 (Task Migration)

线程可以在唤醒时选择另一 CPU,也可以由负载均衡等路径迁移;实际路径受 CPU 集合、调度类和厂商策略约束。

  1. 负载均衡:在适用的调度域内平衡负载或处理容量不匹配。另有空闲大核不代表一定允许把目标线程拉过去。
  2. 唤醒放置:根据保留/衰减的负载估计、其他线程负载等选择 CPU。收到一个大下载任务不会让尚未执行的线程历史 util 立即飙升;工作量突变可能需要后续运行或提示才能反映出来。

线程在大小核之间“反复横跳”,或长时间在较小核心上运行,都值得调查,但不单独证明调度错误。核对关键路径、允许核集合、利用率与性能结果,再判断是否影响截止时间。厂商定制要以对应设备 trace 和实现证据解释,不能从引擎商品名推导选核算法。

实战与 SQL

使用 SQL 进行量化分析

以下查询按 Perfetto v58.3 的调度表及进程/线程视图中的 sched、thread_state、thread 与 process 字段编写,范围限定为单设备 trace。utid/upid 是 trace 内实例身份,不能用 name 或可能复用的 tid/pid 替代。

查询保守排除 dur <= 0:特别是 dur = -1 的未闭合区间不能直接求和。因此结果是已知完整区间的观测时长,丢事件或尾部未闭合可能使它低估;不是自动补齐的总时长。使用前应在目标 Trace Processor 版本检查 schema 和 trace 完整性。

先列出线程及所属进程,按进程、tid、名称和生命周期选中一个 utid,不要用 name = ... LIMIT 1 猜:

1
2
3
4
5
6
7
8
SELECT
t.utid, t.tid, t.name AS thread_name,
p.upid, p.pid, p.name AS process_name,
t.start_ts, t.end_ts
FROM thread AS t
LEFT JOIN process AS p ON p.upid = t.upid
WHERE t.is_idle = 0
ORDER BY p.upid, t.utid;

若改成窗口 [start, end),应累加 max(0, min(ts + dur, end) - max(ts, start)) 的交集时长;只按起始 ts 筛选会漏掉跨边界部分。多设备 trace 还要用 machine/ucpu 区分 CPU。

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

按 upid 区分进程实例,保留 pid/name 便于阅读;同名进程不会被合并。这里只统计能关联到 process 的已知非 idle 线程。

1
2
3
4
5
6
7
8
9
SELECT
p.upid, p.pid, p.name AS process_name,
SUM(s.dur) / 1e9 AS observed_cpu_time_s
FROM sched AS s
JOIN thread AS t ON s.utid = t.utid
JOIN process AS p ON t.upid = p.upid
WHERE s.dur > 0 AND t.is_idle = 0
GROUP BY p.upid, p.pid, p.name
ORDER BY observed_cpu_time_s DESC;

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

先在上面的身份查询中确认目标 utid,再替换 NULL;不替换会返回空结果,不是“该线程耗时为零”。直接保留状态编码,R+ 与 R 分别统计,其他状态也不会丢失。

1
2
3
4
5
6
7
8
SELECT
s.state,
SUM(s.dur) / 1e6 AS observed_time_ms
FROM thread_state AS s
WHERE s.utid = /* replace NULL with the chosen utid */ NULL
AND s.dur > 0
GROUP BY s.state
ORDER BY observed_time_ms DESC;

3. 查找整份 Trace 中 CPU 消耗最高的线程

按 utid 区分线程实例,避免 tid 复用或同名线程合并。这是整份 trace 的已知运行时间排名;窗口分析需要裁剪跨边界切片。

1
2
3
4
5
6
7
8
9
SELECT
t.utid, t.tid, t.upid, t.name,
SUM(s.dur) / 1e9 AS observed_cpu_time_s
FROM sched AS s
JOIN thread AS t ON s.utid = t.utid
WHERE s.dur > 0 AND t.is_idle = 0
GROUP BY t.utid, t.tid, t.upid, t.name
ORDER BY observed_cpu_time_s DESC
LIMIT 20;

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

替换为选定的 utid。运行分布说明实际在哪些 CPU 执行,不等于线程配置的 CPU affinity 掩码;从未运行过的允许核不会出现在结果中。

1
2
3
4
5
6
7
8
SELECT
s.cpu,
SUM(s.dur) / 1e6 AS observed_time_on_cpu_ms
FROM sched AS s
WHERE s.utid = /* replace NULL with the chosen utid */ NULL
AND s.dur > 0
GROUP BY s.cpu
ORDER BY s.cpu;

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

下面保留“按核数归一化”的口径,但分母是 trace 时长 × 有调度记录的不同 CPU 数,结果名明确为 observed capacity。它不是硬件全部核心、在线核时间积分或异构容量加权利用率。核离线、CPU 未采全或 trace 跨整机 suspend 时,这些口径会不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
WITH observed_cpus AS (
SELECT COUNT(DISTINCT cpu) AS n
FROM sched
WHERE dur > 0
)
SELECT
p.upid, p.pid, p.name AS process_name,
100.0 * SUM(s.dur) / NULLIF(
(TRACE_END() - TRACE_START()) * 1.0 * c.n, 0
) AS observed_capacity_percent
FROM sched AS s
JOIN thread AS t ON t.utid = s.utid
JOIN process AS p ON p.upid = t.upid
CROSS JOIN observed_cpus AS c
WHERE s.dur > 0 AND t.is_idle = 0
GROUP BY p.upid, p.pid, p.name, c.n
ORDER BY observed_capacity_percent DESC;

去掉分母中的 c.n 即为单核基准,进程并行时可以超过 100%。频率与核容量没有纳入这个百分比,不能拿它直接比较两款 SoC 的工作量或耗电。

总结

排查 CPU 问题时,先在目标窗口里分开计算 Running、Runnable 和 Sleep;Runnable 长先查调度与竞争,Running 长再看采样热点,Sleep/D 长则查等待对象。频率和核簇提供背景,但单个频点不能替代线程状态与业务窗口的证据。

关于我 && 博客

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

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

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

微信扫一扫

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
    4. 7.4. ADPF / Performance Hint 应该怎么看
  8. 8. CPU Profiling、火焰图与符号化
  9. 9. Linux 内核调度策略:选核与迁移
    1. 9.1. 选核逻辑 (Task Placement)
    2. 9.2. 核心迁移逻辑 (Task Migration)
  10. 10. 实战与 SQL
    1. 10.1. 使用 SQL 进行量化分析
    2. 10.2. 1. 计算各进程的 CPU 总时长
    3. 10.3. 2. 分析单个线程的时间状态分布
    4. 10.4. 3. 查找整份 Trace 中 CPU 消耗最高的线程
    5. 10.5. 4. 查看线程在各 CPU 核心上的运行时间分布
    6. 10.6. 5. 计算各进程的 CPU 利用率
  11. 11. 总结
  • 关于我 && 博客