【性能优化】使用Perfetto定位应用启动性能的瓶颈

Android应用启动优化相关的文章已经有很多人都写过了,但是主要都是聚焦在,为了启动性能都做了哪些改动上,少见有文章会说应该如何分析、识别应用的启动性能。

本篇文章将会结合我个人对Perfetto的实际使用经历,讲解车载应用的启动时间是如何测量得到的,测量出启动时间后,我们又该如何分析出其中的性能瓶颈。

在分析应用的启动性能之前,我们先简单了解一些Android中有关应用启动时间的基础性常识。

应用启动时间

初始显示时间(TTID)

初始显示时间 (TTID,The Time to Initial Display) ,它是从系统接收到启动意图到应用程序显示第一帧界面的时间,也就是用户看到应用程序界面的时间。

测量TTID

当应用程序完成上面提到的所有工作时,可以在logcat中看到以下的日志输出。

/system_process I/ActivityTaskManager: Displayed xxxx/.MainActivity: +401ms

在所有资源完全加载并显示之前,Logcat输出中的Displayed时间指标,省去了布局文件中未引用的资源或应用作为对象初始化一部分创建资源的时间。

有的时候logcat输出中的日志中会包含一个附加字段total。如下所示:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)

在这种情况下,第一个时间测量值仅针对第一个绘制的 activity。total 时间测量值是从应用进程启动时开始计算,并且可以包含首次启动但未在屏幕上显示任何内容的另一个 activity。total 时间测量值仅在单个activity的时间和总启动时间之间存在差异时才会显示。

一些博客中介绍的使用am start -S -W命令来测得的时间,实际上就是初始显示时间。大多数情况下,初始显示时间并不能代表应用真正的启动时间,例如,应用启动时需要从网络上同步最新的数据,当所有的数据加载完毕后的耗时才是真正的启动耗时,这个就是接下来要介绍的TTFD - 完全显示时间

完全显示的时间(TTFD)

完全显示时间 (TTFD,The Time to Full Display) 。它是从系统接收到启动意图到应用程序加载完成所有资源和视图层次结构的时间,也就是用户可以真正使用应用程序的时间。

测量TTFD

方法一:调用reportFullyDrawn()

reportFullyDrawn()方法可以在应用程序加载完成所有资源和视图层次结构后调用,让系统知道应用程序已经完全显示,从而计算出完全显示时间。如果不调用这个方法,系统只能计算出TTID而无法得知TTFD。

system_process I/ActivityTaskManager: Fully drawn xxxx/.MainActivity: +1s54ms

方法二:拆帧

拆帧法是目前计算车载应用启动耗时时最普遍的做法,拆帧法有许多不同的录制、拆帧方式。

常见的有,使用支持60fps的摄像机(支持60fps摄像的手机也可以)拍摄应用的启动视频,再使用Potplay视频播放器查看从桌面点击应用画面完全显示出来的帧数差值,然后除以60就可以得到应用的启动耗时。

以上方法适合测试人员使用,这里介绍另一种更适合开发人员操作的方式:FFmpeg拆帧。

FFmpeg 的下载地址:http://ffmpeg.org/download.html?aemtn=tg-on

首先使用adb连接Android设备,使用录屏指令录制应用启动时的视频。

adb shell screenrecord /sdcard/launch.mp4

使用FFmpeg查看视频的帧数

ffmpeg -i launch.mp4 

如果视频帧数不足60fps,继续使用FFmpeg将视频补帧到60fps.

ffmpeg -i launch.mp4 -filter:v fps=60 output.mp4

将补帧后的视频,每一帧拆成一张图片,然后计算出从桌面点击应用画面完全显示出来的帧数差值即可。

ffmpeg -i output.mp4 output_%04d.jpg

或将补帧后的视频转换成gif动图,使用图片浏览器数帧(MAC OS自带的图片浏览器就可以)。

ffmpeg -i output.mp4 -vf fps=60,scale=320:-1:flags=lanczos -loop 0 output.gif

关于应用的启动时间和测量方式就介绍到这里,更多内容可以参考Android的官方文档「应用启动时间 | 应用质量 | 安卓」,写得非常详细。

值得一提的是,当前主流车载应用的平均启动耗时(以8155平台为例)如下:

  • 冷启动TTFD

第三方大型互联网应用需要控制在2.6s以下,车载系统应用需控制在1.6s以下,

  • 温启动TTFD

普遍需控制在0.8s以下。

以上是我个人的经验,不同的主机厂商肯定会存在高低不同的性能要求。

Perfetto 介绍

Perfetto是Android 10 引入的系统级跟踪工具,支持Android,Linux和Chrome,用于取代Systrace 。相比于ProfilerAGI,它不再局限于应用内,而是可以提供整个系统的运行状态,当我们需要查看应用有没有影响到系统的稳定性和流畅性时,或者反过来用于分析系统对应用运行的影响时,就可以使用Perfetto来进行系统级跟踪和分析。

有关Perfetto的基础内容,可以查看我之前翻译的Android官方视频:【译】现代Android开发技能 - Perfetto入门

Perfetto 快速上手

Perfetto的使用方式有很多,个人建议使用record_android_trace脚本。它是Perfetto提供的一个辅助脚本,可以帮助我们使用adb从Android设备上收集性能数据。这个脚本有以下作用:

  • 自动检测设备上是否有perfetto二进制文件,如果没有,就尝试从GitHub下载并推送到设备上。
  • 自动设置跟踪的配置参数,例如跟踪时间、缓冲区大小、输出文件路径等。
  • 自动执行perfetto命令,并在跟踪完成后将输出文件拉取到电脑上。
  • 自动在浏览器中打开输出文件,让你可以查看和分析跟踪结果。

record_android_trace的下载地址:https://raw.githubusercontent.com/google/perfetto/master/tools/record_android_trace

record_android_trace的用法如下:

./record_android_trace [options] [category1] [category2] ...

其中,options是一些可选的参数,例如:

  • -o OUT_FILE:指定输出文件的路径,如果不指定,默认为 perfetto_trace.pb。
  • -t TIME:指定跟踪的时间,如果不指定,默认为 10 秒。
  • -b SIZE:指定跟踪的缓冲区大小,如果不指定,默认为 32 MB。

category是一些要跟踪的atrace或ftrace类别,可以使用–list查看设备支持的Trace类别,输出结果可能如下:

link@link-PC:~/Desktop$ ./record_android_trace --list
         gfx - Graphics
       input - Input
        view - View System
     webview - WebView
          wm - Window Manager
          am - Activity Manager
          sm - Sync Manager
       audio - Audio
       video - Video
      camera - Camera
         hal - Hardware Modules
         res - Resource Loading
      dalvik - Dalvik VM
          rs - RenderScript
      bionic - Bionic C Library
       power - Power Management
          pm - Package Manager
          ss - System Server
    database - Database
     network - Network
         adb - ADB
    vibrator - Vibrator
        aidl - AIDL calls
       nnapi - NNAPI
         rro - Runtime Resource Overlay
         pdx - PDX services
       sched - CPU Scheduling
         irq - IRQ Events
         i2c - I2C Events
        freq - CPU Frequency
        idle - CPU Idle
        disk - Disk I/O
        sync - Synchronization
       workq - Kernel Workqueues
  memreclaim - Kernel Memory Reclaim
  regulators - Voltage and Current Regulators
  binder_driver - Binder Kernel driver
  binder_lock - Binder global lock trace
   pagecache - Page cache
      memory - Memory
     thermal - Thermal event
         gfx - Graphics (HAL)
         ion - ION allocation (HAL)

例如,如果想要跟踪 sched、gfx和view,输出文件为 trace.perfetto-trace,跟踪时间为 5 秒,缓冲区大小为 16 MB,可以执行以下命令:

./record_android_trace -o trace.perfetto-trace -t 5s -b 16mb sched gfx view

Perfetto 分析启动性能

使用Perfetto分析应用的启动性能非常简单,首先使用record_android_trace抓取应用的启动数据,执行如下指令:

./record_android_trace -o trace.perfetto-trace -t 15s -b 200mb gfx input view webview wm am sm audio video camera hal res dalvik rs bionic power pm ss database network adb vibrator aidl nnapi rro pdx sched irq i2c freq idle disk sync workq memreclaim regulators binder_driver binder_lock pagecache memory gfx ion

15s后record_android_trace会自动帮我们打开浏览器。在Android App Startups一栏展示的就是应用的启动耗时。如下所示,需要注意的是Android App Startups显示的时间是应用的TTID - 初始显示时间

在Perfetto的左侧选择Metrics,然后选择android_startup,点击Run,Perfetto会自动帮我们分析中应用启动时的各项数据,如下所示。

android_startup {
  startup {
    startup_id: 1
    startup_type: "warm"
    package_name: "com.xxx.xxx.weather"
    process_name: "com.xxx.xxx.weather"
    process {
      name: "com.xxx.xxx.weather"
      uid: 1000
      pid: 3376
    }
    zygote_new_process: false
    activity_hosting_process_count: 1
    event_timestamps {
      intent_received: 100680138137
      first_frame: 102167532928
    }
    to_first_frame {
      dur_ns: 1487394791
      dur_ms: 1487.394791
      main_thread_by_task_state {
        running_dur_ns: 1316606193
        runnable_dur_ns: 34121303
        uninterruptible_sleep_dur_ns: 20429636
        interruptible_sleep_dur_ns: 84415940
        uninterruptible_io_sleep_dur_ns: 12221457
        uninterruptible_non_io_sleep_dur_ns: 8208179
      }
      time_activity_manager {
        dur_ns: 16070209
        dur_ms: 16.070209
      }
      time_activity_start {
        dur_ns: 97578437
        dur_ms: 97.578437
      }
      time_activity_resume {
        dur_ns: 833413073
        dur_ms: 833.413073
      }
      time_choreographer {
        dur_ns: 481555469
        dur_ms: 481.555469
      }
      time_inflate {
        dur_ns: 1241538748
        dur_ms: 1241.538748
      }
      time_get_resources {
        dur_ns: 6173178
        dur_ms: 6.173178
      }
      time_verify_class {
        dur_ns: 1675365
        dur_ms: 1.675365
      }
      time_gc_total {
        dur_ns: 82049531
        dur_ms: 82.049531
      }
      time_dlopen_thread_main {
        dur_ns: 15522344
        dur_ms: 15.522344
      }
      time_lock_contention_thread_main {
        dur_ns: 4711976
        dur_ms: 4.711976
      }
      time_jit_thread_pool_on_cpu {
        dur_ns: 375033124
        dur_ms: 375.033124
      }
      time_gc_on_cpu {
        dur_ns: 81314427
        dur_ms: 81.314427
      }
      jit_compiled_methods: 218
      other_processes_spawned_count: 6
    }
    verify_class {
      name: "com.xxx.xxx.weather.service.VoiceActionManager"
      dur_ns: 1675365
    }
    dlopen_file: "/system/priv-app/Weather/Weather.apk!/lib/arm64-v8a/libffavc.so"
    dlopen_file: "/system/priv-app/Weather/Weather.apk!/lib/arm64-v8a/libpag.so"
    dlopen_file: "/vendor/lib64/hw/android.hardware.graphics.mapper@4.0-impl-qti-display.so"
    dlopen_file: "libadreno_utils.so"
    dlopen_file: "/vendor/lib64/hw/android.hardware.graphics.mapper@3.0-impl-qti-display.so"
    dlopen_file: "/vendor/lib64/hw/gralloc.msmnile.so"
    dlopen_file: "libadreno_app_profiles.so"
    dlopen_file: "libEGL_adreno.so"
    system_state {
      dex2oat_running: false
      installd_running: false
      broadcast_dispatched_count: 0
      broadcast_received_count: 0
      most_active_non_launch_processes: "media.codec"
      most_active_non_launch_processes: "app_process"
      most_active_non_launch_processes: "media.hwcodec"
      most_active_non_launch_processes: "/vendor/bin/hw/vendor.qti.hardware.display.allocator-service"
      most_active_non_launch_processes: "/system/bin/audioserver"
      installd_dur_ns: 0
      dex2oat_dur_ns: 0
    }
slow_start_reason: "GC Activity"
slow_start_reason: "Main Thread - Time spent in Running state"
slow_start_reason: "Time spent in view inflation"
  }
}

android_startup是一个用于记录和分析Android应用启动性能的数据结构,它包含了应用启动过程中的各种信息,例如启动类型、启动时间、启动原因、启动依赖、系统状态等。

android_startup的内容是一个protobuf格式的文本,它表示了一个名为com.xxx.xxx.weather的天气应用的启动数据。其中,最重要的是最后一段的slow_start_reason,它向我们展示了应用可能导致启动缓慢的原因,在第三节我们会重点分析。

其他字段的含义如下:

startup_id: 是一个唯一标识符,表示这是第一次启动;

startup_type: 是一个枚举类型,表示这是一个warm(温)启动,即应用进程已经存在,但没有活动在前台;

package_nameprocess_name表示应用的包名和进程名;

process: 表示应用进程的信息,包括名称、用户标识符(uid)和进程标识符(pid);

zygote_new_process: 表示是否通过zygote创建了新进程,这里为false;

activity_hosting_process_count: 表示有多少个活动托管在这个进程中,这里为1;

event_timestamps: 表示各种事件发生的时间戳,例如intent_received表示收到启动意图的时间,first_frame表示显示第一帧画面的时间;

to_first_frame: 表示从收到启动意图到显示第一帧画面所花费的时间和细节,包括总时间、主线程各种状态的时间、各种操作的时间、各种资源的使用情况等;

verify_class: 表示验证类加载的信息,包括类名和时间;

dlopen_file: 表示打开共享库文件的信息,包括文件名;

system_state: 表示系统状态的信息,包括是否有dex2oat或installd在运行、是否有广播发送或接收、哪些非启动进程最活跃等;

Perfetto实践

启动时触发GC

现象slow_start_reason中出现"GC Activity",表示在启动阶段GC活动拖慢了应用程序的启动。

分析:点击【show timeline】返回到Perfetto时间轴界面。在启动时间轴中,可以看到有一个线程名为HeapTaskDaemon,他就是应用程序的GC线程,在启动阶段活跃了约100ms左右,导致activityResume的时间轴也被拉长100ms。为防止是偶发现现象,进行了多次测量,发现该应用启动时必定会触发GC活动。如图所示:

原因:根据时间轴向前分析,发现该应用在启动阶段会加载一个特殊字体,该字体约13MB,经过与应用的开发沟通,确认该字体已经移动到系统层,应用层不必加载该字体。移除字体后,应用启动时不再100%触发GC。

主线程耗时操作

现象slow_start_reason出现 “Main Thread - Time spent in Running state”,表示启动阶段,主线程中执行较多的耗时操作。

原因:这种情况在应用开发时很常见,一些跨进程的获取数据的操作,应用开发人员会很自然的将其放在主线程Activity的OnCreate或onStart方法下执行,这些IPC方法虽然不至于触发ANR,但是会拖慢应用的启动,应该放置到线程池或协程中执行。

OpenDexFilesFromOat耗时

现象slow_start_reason出现"Main Thread - Time spent in OpenDexFilesFromOat*",表示启动阶段,花费了较多时间在读取dex文件上。

原因:这种情况在车载Android系统中较为常见。这可能是因为系统为了加快启动速度,修改了系统中dex2oat的流程,导致此现象,耗时不多的话可以忽略。

这里只是说是“可能”,是因为如今的车载OS为了快速启动,对原生Android的修改非常多,我们需要结合自身的实际情况再做详细分析。

连续多帧绘制超时

现象: 某个应用在Perfetto中的启动时间并不长,大约在1.3s左右,但是使用拆帧法后,发现该应用启动后会有些许卡顿,导致实际启动时间拉长到2.1s。表现在Perfetto上如下所示,在第一帧绘制完毕后,后续2、4、5帧的绘制时间都超过了150ms。

分析:Perfetto给出的帧绘制时间轴显示大部分时间花费在View的Layout上,这说明在首帧绘制完毕后,又触发了多次页面重绘。

原因:通过代码结合应用的日志,发现该应用在启动时会使用空数据刷新一次页面,然后会从IPC接口中再获取一次数据更新页面,而且由于代码缺陷,数据刷新会连续执行4次,导致了该情况。修改缺陷代码,首帧之后就不会再发生连续绘制超时的情况了。

参考资料

https://developer.android.com/topic/performance/vitals/launch-time#time-initial

<think>好的,用户想了解如何检测与图形渲染相关的性能瓶颈,特别是关于图形绘制调用、性能瓶颈的检测方法、工具和分析。首先,我需要整理现有的知识,并参考提供的引用内容来构建回答。 首先,用户提到的引用中有关于Unity Profiler的信息,引用[3]提到Unity Profiler可以分析CPU、内存、渲染等方面的性能数据,这对于图形渲染的性能分析非常相关。应该详细说明Unity Profiler在渲染分析中的应用,比如如何检测Draw Call数量、GPU时间等。 另外,其他引用中提到的工具如JProfiler(引用[1])、Perfetto(引用[2])和Webgrind(引用[4])可能不直接针对图形渲染,但可以间接辅助。例如,JProfiler可以分析CPU使用情况,帮助识别是否渲染相关的代码占用了过多资源。Perfetto虽然主要用于Android系统级分析,但可能也包含GPU相关的性能数据,需要确认这一点。 然后,用户的问题是关于图形绘制调用的瓶颈检测,这里需要明确几个关键点:绘制调用(Draw Call)的数量、批次合并优化、GPU管线分析、显存带宽等。每个部分需要对应的工具或方法。 可能还需要提到其他工具,如RenderDoc,这是一个开源的图形调试工具,可以捕获帧并详细分析每个Draw Call,检查渲染状态和资源使用情况。此外,GPU厂商提供的工具,如NVIDIA Nsight、AMD Radeon GPU Profiler、Intel GPA等,这些工具能深入分析GPU性能,帮助识别瓶颈所在。 另外,图形API本身的性能分析工具,比如OpenGL的GL_KHR_debug扩展,或者Vulkan的验证层,可以在开发过程中帮助捕获性能问题。不过这些可能更偏向于开发阶段,但用户可能也需要了解。 需要强调检测流程:首先确定是CPU还是GPU瓶颈,如果是CPU,可能需要优化Draw Call数量,使用批处理技术;如果是GPU,可能需要优化着色器、减少纹理大小、降低分辨率等。 在引用中提到的Unity Profiler是一个很好的起点,因为它直接集成在引擎中,适合大多数开发者。另外,需要提到如何利用这些工具的具体步骤,比如在Unity中如何打开Profiler,查看渲染统计数据等。 可能还需要考虑不同平台的分析工具,比如移动端使用AndroidPerfetto或Systrace,iOS使用Xcode的Instruments工具。这些工具可以提供系统级的GPU和CPU性能数据,帮助识别渲染瓶颈。 总结回答结构:首先介绍常用的检测方法,如Draw Call分析、GPU管线分析、资源使用监控等;然后列举相关工具,如Unity Profiler、RenderDoc、厂商工具等;最后给出分析流程,帮助用户逐步定位问题。 需要确保回答符合用户的要求,结构清晰,分步骤说明,并引用提供的参考资料中的相关内容,比如引用[3]中的Unity Profiler部分,以及可能其他引用中的工具作为辅助。</think>### 图形渲染性能瓶颈检测方法及工具分析 #### 一、检测方法与核心指标 1. **绘制调用(Draw Call)分析** 绘制调用是CPU向GPU发送的渲染指令。**Draw Call数量过多**会导致CPU负载升高,成为性能瓶颈。 - **优化方法**:使用批量渲染(Batching)合并相同材质的对象,减少Draw Call数量。 - **检测工具**:Unity Profiler、RenderDoc。 2. **GPU管线分析** GPU性能瓶颈常出现在顶点处理、片元着色器或光栅化阶段。 - **关键指标**:GPU时间(每帧耗时)、显存带宽占用、着色器复杂度。 - **检测工具**:NVIDIA Nsight、AMD Radeon GPU Profiler。 3. **资源使用监控** 高分辨率纹理、复杂模型或未压缩资源会占用显存和带宽,导致渲染延迟。 - **检测方法**:监控显存占用率、纹理加载时间。 - **工具示例**:Unity Profiler的**Memory模块**[^3]。 --- #### 二、核心工具详解 1. **Unity Profiler** - **功能**:实时分析CPU、GPU、内存等模块性能定位渲染瓶颈。 - **关键视图**: - **Rendering模块**:显示Draw Call数量、批处理效率、三角形数量。 - **GPU模块**:分析GPU时间分布(如着色器计算、后处理耗时)。 - **操作步骤**: ```markdown 1. 在Unity编辑器中打开`Window > Analysis > Profiler`。 2. 启动游戏并记录性能数据。 3. 检查`Rendering`和`GPU`标签下的指标。 ``` 2. **RenderDoc** - **用途**:捕获单帧渲染过程,逐层分析Draw Call、纹理状态和着色器输出。 - **典型场景**: - 检查过度绘制(Overdraw)问题。 - 验证材质参数是否正确传递至GPU。 3. **硬件厂商工具** - **NVIDIA Nsight**:支持DirectX/Vulkan/OpenGL,提供GPU时间轴分析和着色器调试。 - **Intel GPA**:针对集成显卡优化,识别显存带宽瓶颈。 --- #### 三、分析流程 1. **定位瓶颈类型** - 若**CPU主线程耗时高**,检查Draw Call数量与脚本逻辑。 - 若**GPU耗时高**,分析着色器复杂度或后处理效果。 2. **优化验证** - 使用工具对比优化前后数据,例如: $$ \text{优化后帧率} = \frac{1}{\text{优化前单帧时间} - \Delta t} $$ 其中$\Delta t$为减少的CPU/GPU耗时。 3. **跨平台适配** - **移动端**:使用Android Perfetto[^2]或Xcode Instruments分析GPU负载。 - **PC/主机**:依赖硬件厂商工具深入调优。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值