【性能优化】--perfetto分析思路

这里写目录标题

目录

在 Perfetto 中分析"一帧完整绘制过程"时,核心是追踪从应用发起绘制请求屏幕最终渲染 的全链路,涉及 Android 系统的三大核心组件:应用进程(App)SurfaceFlinger(合成服务)显示硬件(如 GPU、屏幕刷新)。以下是具体分析方法:

一、一帧完整绘制的关键阶段

正常情况下,一帧的完整流程包含 4 个核心步骤(简称"渲染流水线"):

  1. 应用绘制(App Draw):应用进程通过 Canvas 或 OpenGL/ Vulkan 绘制 UI 元素(如 View 布局、自定义绘制)。
  2. 提交缓冲区(Buffer Queue):应用将绘制结果(图形缓冲区)提交到 SurfaceFlinger 的 BufferQueue。
  3. 合成(Compose):SurfaceFlinger 接收多个应用的缓冲区,进行图层合成(如叠加、透明度处理)。
  4. 显示(Present):合成结果发送到显示硬件(如通过 HWC 硬件合成),最终在屏幕上刷新显示。

这一过程需匹配屏幕刷新率(如 60Hz 对应每帧约 16.6ms),超过该时间会导致卡顿。

二、在 Perfetto 中搜索关键事件与函数

需通过过滤以下事件类型函数/进程名,定位一帧的完整链路:

1. 应用进程(App)的绘制阶段
  • 核心进程 :目标应用进程(如 com.example.myapp)。
  • 关键事件/函数
    • Choreographer#doFrame:系统向应用发送的帧回调(触发应用开始绘制),是一帧的起点。
      • 搜索关键词:Choreographer.doFrameandroid.animation.Choreographer.doFrame
    • ViewRootImpl#performTraversals:应用执行测量(measure)、布局(layout)、绘制(draw)的入口。
      • 搜索关键词:ViewRootImpl.performTraversals
    • Canvas.draw*:应用通过 Canvas 绘制的具体操作(如 drawRectdrawText)。
    • OpenGL/Vulkan 调用:若使用硬件加速,会有 glDrawArraysvkQueueSubmit 等 GPU 绘制函数。
2. 缓冲区提交阶段
  • 核心进程 :应用进程 + surfaceflinger 进程。
  • 关键事件/函数
    • Surface#queueBuffer:应用将绘制完成的缓冲区提交给 BufferQueue。
      • 搜索关键词:Surface.queueBufferandroid.view.Surface.queueBuffer
    • BufferQueueProducer#queueBuffer:BufferQueue 接收应用提交的缓冲区(SurfaceFlinger 侧)。
3. SurfaceFlinger 合成阶段
  • 核心进程surfaceflinger(系统合成服务)。
  • 关键事件/函数
    • SurfaceFlinger::onMessageInvalidate:SurfaceFlinger 收到缓冲区更新,触发合成。
    • SurfaceFlinger::commit:执行图层合成(软件合成或硬件合成)。
    • HWComposer::presentDisplay:若使用硬件合成(HWC),将合成结果提交给显示硬件。
4. 显示刷新阶段
  • 核心事件
    • vsync:垂直同步信号(屏幕刷新的时间基准),每帧一次,由硬件触发。
      • 搜索关键词:vsyncVSYNC(通常在 ftrace 事件中以 vsync-appvsync-sf 区分应用侧和 SurfaceFlinger 侧的 VSYNC)。
    • display_hw 相关事件:显示硬件的刷新完成事件(如 hwc_commit)。

三、具体分析步骤

以"分析某应用的一帧绘制流程"为例:

1. 定位目标时间段
  • 加载包含应用交互的 trace 文件(如滑动页面时的追踪)。
  • 在左侧 Processes 中展开目标应用进程(如 com.example.myapp)和 surfaceflinger 进程,显示其线程活动。
2. 找到"一帧的起点":Choreographer.doFrame
  • 在顶部搜索框输入 Choreographer.doFrame,主视图会高亮所有应用侧的帧回调事件。
  • 点击某一个 doFrame 事件块,右侧详情面板会显示其开始时间(如 t=1000ms)和线程(通常是应用的 main 线程)。
3. 追踪应用绘制链路
  • doFrame 开始,在应用 main 线程的时间轴上,向后查找:
    • 若出现 ViewRootImpl.performTraversals 事件,说明进入测量、布局、绘制阶段。
    • 后续若有 Canvas.draw*glDrawArrays 等事件,对应具体绘制操作。
    • 绘制结束后,会出现 Surface.queueBuffer 事件,标志应用提交缓冲区完成(记录此时时间 t1)。
4. 追踪 SurfaceFlinger 合成链路
  • 切换到 surfaceflinger 进程的线程(通常是 main 线程),查找 BufferQueueProducer.queueBuffer 事件(对应应用提交的缓冲区到达 SurfaceFlinger,时间 t2)。
  • 后续会出现 SurfaceFlinger.commitHWComposer.presentDisplay 事件,标志合成完成(时间 t3)。
5. 确认帧显示完成:VSYNC 与显示事件
  • 在左侧 CPUsEvents 中查找 vsync 事件,合成完成后下一个 vsync 信号会触发屏幕刷新(时间 t4)。
  • 一帧的总耗时为 t4 - t0t0 为上一帧的 vsync 时间),若超过 16.6ms(60Hz)则可能卡顿。
6. 分析瓶颈
  • 应用绘制耗时过长 :若 performTraversalsqueueBuffer 时间过长(如 >10ms),说明应用布局复杂或绘制逻辑低效(需优化 View 层级、减少过度绘制)。
  • 合成耗时过长surfaceflingercommitpresentDisplay 时间过长,可能是图层过多或硬件合成能力不足(需减少图层、使用 SurfaceControl 优化)。
  • VSYNC 对齐问题 :若帧提交时间错过 VSYNC 信号,会导致多等待一帧(如本应 16ms 显示,实际 32ms),需检查应用是否及时响应 doFrame

四、实用工具与技巧

  1. 使用"Frame Timeline"视图

    若 trace 包含 android.frame_timeline 数据源(需在配置中添加),左侧会出现 Frame Timeline 追踪项,直接显示每帧的开始/结束时间、总耗时及各阶段分解(如 AppSFPresent 耗时),一目了然。

  2. 通过 SQL 统计帧耗时

    在 Perfetto 的 SQL 编辑器中执行以下查询,统计应用每帧的总耗时:

    sql 复制代码
    SELECT
      ts / 1e6 AS start_ms,  -- 帧开始时间(毫秒)
      dur / 1e6 AS frame_duration_ms,  -- 帧总耗时(毫秒)
      package  -- 应用包名
    FROM android_frame_timeline_slice
    WHERE package = 'com.example.myapp'
    ORDER BY start_ms;

    frame_duration_ms 频繁 >16.6,则存在卡顿。

  3. 对比正常帧与卡顿帧

    找到耗时正常的帧(如 10ms)和卡顿帧(如 30ms),对比两者在应用绘制、合成阶段的事件差异,定位卡顿帧中额外的耗时操作(如突然出现的大对象绘制、锁等待)。

通过以上方法,可清晰拆解一帧的完整流程,并定位各阶段的性能瓶颈。核心是抓住 Choreographer.doFrame(起点)、queueBuffer(应用提交)、SurfaceFlinger 合成、VSYNC(显示)这几个关键节点,结合时间差分析耗时。

送显

在 Android 性能分析(尤其是图形渲染和帧显示流程)中,"送显" 是指将最终合成的图像数据提交给显示硬件(如屏幕、显示屏控制器),并由硬件完成实际像素刷新的过程,是一帧画面从"系统处理完成"到"用户可见"的最后一步。

送显的核心作用

在帧渲染的全链路中,应用进程完成绘制、SurfaceFlinger 完成图层合成后,得到的是一帧完整的图像数据(通常存储在图形缓冲区中)。送显就是将这一缓冲区的数据"交付"给显示硬件,触发屏幕的物理刷新,让用户看到这一帧画面。

送显的关键环节

  1. 合成结果提交

    SurfaceFlinger 完成图层合成后(无论是软件合成还是硬件合成 HWC),会将最终的图像缓冲区通过 HWComposer(硬件合成器) 提交给显示控制器(Display Controller)。

    • 对应关键事件:HWComposer::presentDisplay(Perfetto 中可搜索该函数名追踪)。
  2. 显示硬件刷新

    显示控制器接收到缓冲区后,会等待下一个 VSYNC(垂直同步信号),在信号触发时将缓冲区数据发送到屏幕的物理像素,完成实际显示。

    • VSYNC 是屏幕刷新的时间基准(如 60Hz 屏幕每 16.6ms 一次),确保画面刷新与屏幕扫描节奏一致,避免画面撕裂。

送显在 Perfetto 中的分析关注点

  • 送显时机 :通过搜索 vsync 事件和 presentDisplay 事件,查看合成完成到送显的时间间隔。若间隔过长,可能导致帧显示延迟(错过 VSYNC 信号,需多等待一帧)。
  • 送显成功率 :若频繁出现 hwc_commit_failed 等事件,可能是硬件合成失败或缓冲区异常,导致画面卡顿或闪烁。
  • 送显耗时:正常情况下送显应在 1-2ms 内完成,若耗时过长(如超过 5ms),可能是显示硬件性能不足或驱动问题。

简言之,"送显"是连接系统图形处理与用户视觉感知的最后一步,直接影响帧显示的及时性和流畅度。

在 Perfetto 的 trace 中分析卡顿问题时,核心是先通过帧耗时判断卡顿类型,再定位卡顿发生的阶段(应用绘制、合成、送显等),最后聚焦具体函数或事件作为切入点深入分析。以下是具体方法:

一、先确定卡顿类型:按"帧耗时超标原因"分类

卡顿的本质是单帧耗时超过屏幕刷新率对应的阈值(如 60Hz 屏幕单帧应 ≤16.6ms,90Hz 应 ≤11.1ms)。根据卡顿发生的环节,可分为以下几类:

卡顿类型 核心特征(Perfetto 中表现) 常见原因
应用绘制卡顿 应用进程(如 com.example.app)的 main 线程耗时过长(doFramequeueBuffer 阶段 >10ms) 布局复杂、过度绘制、主线程做耗时操作(如 IO、大量计算)、自定义 View 绘制低效等。
合成卡顿 SurfaceFlinger 进程(surfaceflinger)的合成阶段耗时过长(queueBufferpresentDisplay 阶段 >5ms) 图层过多(>4 层)、硬件合成(HWC)失败降级为软件合成、图层格式不兼容导致额外转换等。
送显卡顿 合成完成到屏幕显示的时间间隔过长(错过 VSYNC 信号,导致多等待一帧) 合成完成时间晚于 VSYNC 信号、显示硬件(如 GPU、显示屏控制器)响应慢、缓冲区争夺等。
系统资源卡顿 应用或 SurfaceFlinger 线程被阻塞(如锁等待、CPU 资源被抢占),导致无法及时处理帧任务 其他进程占用过高 CPU(如后台进程频繁唤醒)、线程优先级低被抢占、锁竞争(如 synchronized 阻塞)等。

二、分析切入点:从"帧链路关键节点"定位卡顿阶段

1. 第一步:找到卡顿帧,确定总耗时
  • 通过 Frame Timeline 快速定位 (推荐):

    若 trace 包含 android.frame_timeline 数据源(配置时需添加),左侧 Frame Timeline 追踪项会直接显示每帧的:

    • 总耗时(Duration):若超过 16.6ms(60Hz),即为卡顿帧。
    • 各阶段耗时分解:App(应用绘制)、SF(SurfaceFlinger 合成)、Present(送显)。
      例如:某帧 App 耗时 20ms,SF 耗时 3ms,Present 耗时 1ms → 确定为应用绘制卡顿
  • 手动追踪帧链路 (无 Frame Timeline 时):

    Choreographer.doFrame(应用帧起点)为基准,到下一个 VSYNC 信号(显示终点)的时间差即为单帧耗时。若超过阈值,标记为卡顿帧。

2. 第二步:按卡顿类型定位切入点
(1)应用绘制卡顿:从应用主线程的"耗时操作"切入
  • 核心追踪目标 :应用 main 线程的 doFrameperformTraversalsqueueBuffer 链路。
  • 切入点操作
    1. 在卡顿帧的 doFrame 事件后,查看应用 main 线程的时间轴,找到持续时间长的函数块(超过 5ms 的事件)。
    2. 重点搜索以下关键词,定位具体耗时操作:
      • ViewRootImpl.performTraversals:若耗时过长(如 >8ms),说明测量(measure)、布局(layout)、绘制(draw)阶段有问题。
        • 展开该事件,查看子事件:measure 耗时高 → 布局层级过深或 onMeasure 逻辑复杂;draw 耗时高 → 过度绘制或 onDraw 中做耗时计算。
      • Canvas.draw*glDrawArrays:若某绘制函数(如 drawBitmap)耗时过长 → 图片过大或绘制频繁。
      • Handler.dispatchMessageRunnable.run:若主线程执行了非 UI 操作(如 new Thread 意外在主线程运行)→ 阻塞绘制。
      • Binder.call:若主线程频繁调用 Binder(如跨进程通信)→ IPC 耗时阻塞绘制。
(2)合成卡顿:从 SurfaceFlinger 的"图层处理"切入
  • 核心追踪目标surfaceflinger 进程的 main 线程(合成主线程)或 RenderEngine 线程(软件合成)。
  • 切入点操作
    1. 查看卡顿帧中 surfaceflingerqueueBuffer(接收应用缓冲区)到 presentDisplay(提交给硬件)的时间差,若 >5ms 则为合成卡顿。
    2. 重点搜索以下关键词:
      • SurfaceFlinger::commit:若耗时过长 → 图层合成逻辑复杂,可进一步查看是否有 SoftwareComposer 相关事件(说明硬件合成失败,降级为软件合成,耗时更高)。
      • Layer::prepareLayer::composite:若某图层处理耗时高 → 该图层可能过大(如全屏图层)、格式不兼容(如 YUV 转 RGB)或有透明度过高(需混合计算)。
      • HWComposer::validateDisplay:若耗时高 → 硬件合成器(HWC)处理图层时出现瓶颈,可能是图层数量超过硬件限制(如超过 4 层)。
(3)送显卡顿:从"VSYNC 对齐"和"缓冲区"切入
  • 核心追踪目标vsync 信号、presentDisplay 事件、BufferQueue 状态。
  • 切入点操作
    1. 查看合成完成时间(presentDisplayts)与下一个 vsync 信号的时间差:
      • 若合成完成时间在 vsync 信号之后 → 错过本次刷新,需等待下一帧(多 16.6ms),属于送显延迟
    2. 搜索 buffer_queue 相关事件:
      • 若出现 dequeueBuffer blocked(申请缓冲区阻塞)→ 应用或 SurfaceFlinger 缓冲区不足(如未及时释放),导致送显等待。
      • acquireBuffer 耗时过长 → SurfaceFlinger 获取应用缓冲区延迟,可能是缓冲区被占用(如应用未及时提交)。
(4)系统资源卡顿:从"线程阻塞"和"CPU 调度"切入
  • 核心追踪目标 :应用/surfaceflinger 线程的状态(如 Running/Sleeping/Uninterruptible)、CPU 核心负载、锁事件。
  • 切入点操作
    1. 查看卡顿帧期间,应用 main 线程或 surfaceflinger 线程是否频繁处于 SleepingUninterruptible 状态:
      • Sleeping 时间长 → 可能被低优先级线程抢占,或等待锁(如 synchronized 锁被其他线程持有)。
      • 搜索 sched_switch 事件:查看线程被切换出去的原因(prev_state=S 表示休眠),以及切换进来的线程(是否被高优先级线程抢占)。
    2. 查看 CPU 核心负载(左侧 CPUs 展开各核心):
      • 若某核心长期 100% 占用(绿色块填满时间轴)→ 可能是其他进程(如后台服务)占用过多 CPU,导致应用线程无法调度。
      • 搜索进程名:通过顶部搜索框查找高 CPU 占用的进程(如 com.android.systemui 异常活跃)。

三、实战技巧:快速缩小范围

  1. 对比正常帧与卡顿帧

    找到同一操作下的正常帧(如 10ms)和卡顿帧(如 30ms),对比两者在应用绘制、合成、CPU 调度上的差异。例如:卡顿帧中多了一个 onDraw 耗时 20ms 的事件 → 切入点就是该 onDraw 函数。

  2. 利用 SQL 统计耗时TOP事件

    在 Perfetto 的 SQL 编辑器中执行查询,定位应用主线程中耗时最长的函数:

    sql 复制代码
    SELECT
      name,  -- 函数名
      SUM(dur) / 1e6 AS total_ms,  -- 总耗时(ms)
      COUNT(*) AS count  -- 调用次数
    FROM slice
    WHERE utid IN (SELECT utid FROM thread WHERE name = 'main' AND process.name = 'com.example.app')
    GROUP BY name
    ORDER BY total_ms DESC
    LIMIT 10;

    结果中排名靠前的函数(如 View.draw)即为重点分析对象。

  3. 结合日志定位场景

    若 trace 包含 android.log 数据源,可通过搜索应用日志(如 D/MyApp: 开始滑动)定位卡顿发生的业务场景(如滑动列表、弹窗显示),再聚焦该场景下的帧链路。

总结

  1. 先通过 Frame Timeline 或帧链路确定卡顿帧的总耗时及各阶段占比,划分卡顿类型(应用/合成/送显/资源)。
  2. 针对不同类型,聚焦对应进程(应用/surfaceflinger)和线程(main 线程/合成线程)。
  3. 搜索关键函数(如 performTraversalscommit)或事件(vsyncsched_switch),找到耗时最长的操作作为切入点,逐步拆解具体代码或逻辑问题。

通过这种"定位类型→聚焦阶段→拆解操作"的流程,可高效定位卡顿根源。

相关推荐
摸着石头过河的石头6 小时前
深入理解JavaScript事件流:从DOM0到DOM3的演进之路
前端·javascript·性能优化
allk559 小时前
List && Map在安卓中的优化
android·数据结构·性能优化·list·map
Dontla12 小时前
React useCallback介绍(用来缓存函数的引用,避免每次渲染都重新创建函数)主要用于性能优化
react.js·缓存·性能优化
逻极19 小时前
HarmonyOS 5 鸿蒙应用性能优化与调试技巧
华为·性能优化·harmonyos·鸿蒙
言德斐20 小时前
SQL性能优化的思路及策略
数据库·sql·性能优化
Lion Long1 天前
PB级数据洪流下的抉择:从大数据架构师视角,深度解析时序数据库选型与性能优化(聚焦Apache IoTDB)
大数据·性能优化·apache·时序数据库·iotdb
维诺菌1 天前
k8s java应用pod内存占用过高问题排查
java·jvm·云原生·容器·性能优化·kubernetes
鼠鼠我捏,要死了捏1 天前
深入解析Spring Boot热部署与性能优化实践
spring boot·性能优化·热部署
顾林海1 天前
Android UI优化:让你的APP从“卡顿掉帧”到“丝滑如德芙”
android·面试·性能优化