百度 App 低端机优化 - 启动性能优化(概述篇)
一、前言
移动互联网人口红利见顶,用户增长放缓,移动互联网各公司都在经历从服务好增量用户到服务好存量用户的思维转变,而低端机又是存量用户中重要的组成部分,因此低端机的性能优化变得尤为重要。
性能优化是最重要的优化体验的技术手段,重理论与实践相结合,技术挑战大,百度 App 性能团队也因此成立低端机专项攻克体验难题。本专题以低端机启动性能优化的视角,覆盖技术架构上的性能设计、性能问题定位、防劣化机制等方面最佳实践,最终形成性能优化闭环。
二、价值
以低端机为重点,辐射中高端机,通过技术和产品上的深度优化,可感知的提升用户体验,实现扩大用户规模、提升留存和提升收入的目标。
三、难点
低端机性能问题复杂:百度 App 启动流程复杂,低端机启动过程中黑屏、卡顿等问题非常严重;
缺乏整体调度机制:涉及业务众多,较多预加载任务,需要平衡研发性能和产品业务指标;
问题定位成本高:现有性能工具,无法高效的发现、定位性能问题,归因分析成本很高;
监控机制不完善:缺乏完善的防劣化机制,只能依靠部分线上打点和用户反馈发现问题。
四、拆分
项目之初有几个灵魂拷问:
标准疑问:如何定义低端机?
结合百度 App 产品现状和手机配置分布,参考业界相关设计,自建低端机标准,采用评分机制,保障评分客观和稳定,保障低端机占比的合理性;
主场景及指标疑问:如何挖掘优化场景并抽象指标,衡量低端机性能?
结合应用商店用户反馈、百度 App 反馈和论坛反馈,决定低端机优化主场景为点 icon 启动场景,次场景为端外调起场景,后续在应用全生命周期投入;
基于场景制定反映用户实际体验的性能指标,指导性能优化,量化性能工作;
提效疑问:如何快速发现性能问题?
借助工具提效,自建稳定且高效性能工具基于目前已有工具二次开发,提高发现问题效率;
优化疑问:如何系统化的优化性能问题?
建设应用级的基础调度机制,服务于业务,并协助业务优化性能;
挖掘应用级的基础机制痛点,优化并升级基础机制,快速提升性能;
防劣化疑问:如何避免性能优化的同时出现劣化问题,并打造劣化问题修复自运转的飞轮?
建设全研发流程的问题发现与定位的基础机制,简称版本防劣化;通过自动化测试和自助化文档服务,做到自动化测试、自动化分析、自动化分发和自助化解决,建设指标的线上线下可观测体系。
基于上述疑问的理解,将低端机启动性能优化拆分为 3 个子方向,分别为观测设施、基础设施和业务优化。
观测设施:建设低端机标准,建设启动性能衡量指标;建设线上、线下防劣化机制,实现线下随版的性能问题前置和线上问题的自动化分析与归因;
基础设施:三驾马车,高效性能工具、高性能组件、调度框架。高效性能工具主要包括 Trace/Hook/TimeProfiler,服务于快速发现并自动化性能问题;高性能组件主要优化手百基础性能并赋能业务,突破系统约束与瓶颈,建设行业内领先的基础设施;调度机制作为优化的核心手段,业务初始化任务可通过接入调度器快速实现性能优化;
业务优化:根据工具输出性能问题,协同业务优化不规范耗时、不合理“预”,初始化任务通过接入调度框架方式达到优化效果。
本文为低端机优化-启动优化项目的概述篇,整体描述下观测设施、基础设施和业务优化三个方面,后续会陆续发布性能工具 2 篇(Trace 工具+Thor Hook 工具)、性能优化 1 篇、防劣化体系 1 篇。
丨 4.1 观测设施
丨 4.1.1 低端机标准
低端机定义,目前是按照机型评分 PV/UV 占比低于 15%的评分定义为低端机,机型评分有:静态评分、动态评分和综合评分(静态评分和动态评分加权值)方案,目前线上百度 App 以静态评分为主,动态评分受运营等影响有波动,业务可基于动态评分判断设备当前负载情况。基于 Android 和 iOS 机型分布差异,Android 以模型计算为主,iOS 以配置表为主。
静态评分
Android:机型种类繁多,无法枚举,因此需通过手机的硬件配置信息(CPU/GPU/内存等),利用训练的数据模型,多维度计算给出一个综合评分,低端机占大盘比例 15%。
iOS:机型种类优先,可枚举,因此可通过配置表直接读取机型评分分数,低端机占大盘比例 15%。
动态评分
通过本地收集应用性能指标,目前实现了性能维度(启动速度为主)作为判断依据,线上收集大盘数据,并拟合生成启动时间-性能评分,生成模型,通过传入性能指标参数,得出动态评分。
综合评分
根据静态得分和动态得分,按照权重计算,得出最终得分。
静态得分* weight + 动态得分 * (1 - weight) = 设备最终得分启动性能指标。
丨 4.1.2 启动性能指标建设
TTI (Time To Interactive),代表用户可输入的时间。TTI 概念来源于 Web,先来看下面这张图:
图中涉及几个关键名词,FCP (First Contentful Paint) 首次内容绘制,FID (First Input Delay) 首次输入延迟,从图中图示可知 TTI 指标基本可代表主线程的流畅程度。
Web 端 TTI 指标优劣评估如下,来源于https://web.dev/interactive/。
基于 Web 端性能指标,结合移动端现状抽象 TTI 指标,衡量应用打开后,用户可输入的时间,主要包括常规启动 TTI 衡量点击 icon 进入手百性能,首次安装启动 TTI 用于衡量新用户首次安装启动应用的启动性能,在百度 App 上 TTI 起点为应用进程创建时间戳,比较接近于点击桌面的时间戳,结束点为主线程首次空闲。
长久以来,开发者为了追求更快的渲染速度而对页面进行优化,但有时,这会以牺牲 TTI 为代价。当用户尝试与看似具备交互性但实际上并非如此的页面进行交互时,用户可能会有如下两种反应:
在最好的情况下,用户会因为页面响应缓慢而感到恼火。
在最坏的情况下,用户会认为页面已损坏,因此很可能直接离开,他们甚至可能对您的品牌价值丧失信心或信任。
因此,为了避免这个问题,需尽一切努力将页面渲染完成时间和 TTI 之间的差值降至最低。
丨 4.1.3 防劣化机制建设
优化犹如进攻,防劣化犹如防守,只有做到攻守兼备,才能稳健优化。如何搭建防劣化机制,观测线下和线上的问题及指标情况,变得尤为重要。线下防劣化是第一环,及时发现劣化问题并提升问题分析效率,保障测试覆盖度,尽量覆盖用户的核心场景;线上防劣化则是在线下未发现问题并上线后,快速发现并快速定位线上问题。本文中会做简短阐述,后续会有专文来阐述这块内容。
线下防劣化
在代码主线分支(如 master 分支)或者研发同学开发分支,可提交防劣化任务自动化测试,目前百度 App 已实现 Daily 的自动化测试与分发,每个版本防劣化基本不需要投入人力,流水线自动分发,业务同学自助式解决问题。
Daily 测试中主要有如下几个步骤:
打包自动化:通过打包流水线和性能工具自动化脚本,实现流水线自动编译插桩、打包;
测试自动化:通过 Docker 镜像实现快速部署和迁移,通过 Appium 自动化测试框架,执行定制化 case 实现启动场景真机自动化测试;
分析自动化:通过脚本生成性能数据(Trace/Timeprofiler),脚本对比分析启动耗时、堆栈反混淆,产出劣化报表(或者 svg 图)的自动化;
分发自动化:通过脚本对劣化问题进行去重、置信度过滤,然后通过分发服务进行问题归属定位,分发;
线上防劣化
旨在快速发现已带到线上的劣化问题分析与归因,主要包括实验防劣化和函数级防劣化。
实验防劣化
实验在线下比较难测试完全,极有可能在实验分支放量期间,线上数据出现变化,需结合实验平台,打通数据平台,观测线上核心指标变化,此部分作为线上防劣化的关键一环。
在实际生产中,发生过多次实验放量导致的劣化,均通过此方式定位并归因,具体实验数据表现如下:
备注:业务显著性需要观察多日数据,截图为一日数据。
函数级防劣化
函数级防劣化主要指函数级别打点报表观测能力建设与自动化分析建设。
函数级别打点报表观测能力建设:日常性能相关打点有 sdk 和业务调用两部分,较为普遍的实现方式为业务代码依赖打点 sdk,接收打点数据后做数据上报,这种方式可以实现功能,但监控模块在中台输出组件时会是棘手问题,因此需要解除依赖。目前线下防劣化已实现自动化插桩,只是插桩逻辑为 Trace.beginSection 类似此种函数调用,业务无感知,因此解除依赖部分同样可以采用类似方式,但需要对插桩数据做控制, 否则包体积会出现较大增长。因此,基于配置文件的插桩,将打点 sdk 入口插入到需监控的业务方法中,即完成了与业务的解耦,也控制了包体积的增长。在确定解耦方案后,如何确定插桩函数 List 也是比较关键一环,关系到线上问题定位,在确定函数插桩列表时,需结合 trace 分析结果监控主线程长耗时任务和子线程核心任务,同时也需主动监控运营相关接口,保障数据波动时可归因。打点 sdk 中囊括维度主要有“函数调用耗时“和”函数开始执行时间戳“,在线上统计时会形成“函数执行 PV 维度”,线上报表可直观查阅 3 个维度相关信息。
自动化分析建设:通过“函数调用耗时“、”函数开始执行时间戳“和“函数执行 PV”3 个维度相关信息,基本可实现线上问题的自动化分析与归因,整体逻辑为:在自动化分析中,首先通过函数开始时间戳维度可以诊断出哪些业务执行开始时间变慢,在日常问题定位中,极有可能会出现监控函数耗时无法明确诊断出问题,主要原因为函数级别监控为有限个函数统计,此部分监控会与主观认知相关,此时可借助函数开始时间戳维度和函数打点的 PV 维度,来看哪部分执行 PV 或者执行时间戳变慢。总体流程如下图:
丨 4.2 基础设施
基础设施是项目中最核心的组成部分,主要工作有高效性能工具、高性能组件和调度框架,本文中会做简短阐述,后续会有专文来阐述这块内容。
丨 4.2.1 高效性能工具
TraceView/CPU Profiler 性能损耗较大,原生 Systrace 无法定位应用程序代码问题,随着优化逐渐进入深水区,如何快速定位和挖掘问题,成为日常工作中急需解决的问题。百度 App 在项目中开发了两套工具,分别为 Trace 工具和 Hook 工具,Trace 工具以 ASM 插桩为基础,结合 Perfetto 相关能力,做到可视化和自动化分析;Hook 工具以 Epic、Xhook 等 Hook 能力为基础,设计插件框架,实现插件级的 Hook 能力,支持热插拔,整体架构图如下:
通过工具的建设,Trace 工具可准确反映性能问题,分析效率提升明显,可发现锁、长耗时等问题,支持版本对比,为防劣化提供有力支持;Hook 工具完备 Hook 能力,即用即插拔,可输出 I/O、线程等系列问题。在工具落地中,支持了低端机启动性能项目外,也支持了诸多项目的顺利落地。关于工具的细节可关注公众号后续发稿。
丨 4.2.2 高性能组件
在低端机优化-启动性能优化中挖掘出诸多基础组件的性能问题,比如系统原生组件性能差导致业务性能差问题,比如基础组件随着版本迭代或使用业务方逐渐增多导致性能差问题,比如引入的第三方 SDK 导致的性能问题等,对诸多基础组件做了基础改造,如 Eventbus 事件库,Fresco 图片库、网络库等、也对系统相关机制做了优化,如 ContentProvider、FileProvider 等,本文中简单阐述两个比较有代表性的优化:SharedPreferences 优化和 ABTest 锁优化,在后续文章中会对优化项做详细阐述。
SharedPreferences 优化
原生 SharedPreferences 有首次读取性能差、创建线程多、卡顿/ANR,多进程支持差等缺点,从启动性能角度看 SP 加载时 wait 时长占比较大,主要原因是该线程加载 SP 时,会新创建异步线程执行文件加载,此时该线程处于 wait 状态,直到文件加载完成后该线程才可继续执行,须对此种情况做针对性优化。
通过调研 Google DataStore、腾讯 MMKV、滴滴 Booster、头条等业界对 KV 存储做的相关优化,并参考其实现,最终在百度 App 中落地 UniKV,突破系统限制,彻底解决原生 SharedPreferences 有首次读取性能差、创建线程多、卡顿/ANR、多进程支持差等缺点。
核心实现主要有:
高性能的数据迁移框架,非阻塞式数据迁移方式,业务无感知;
支持强类型存储,记录完备数据,保障 getAll 接口可正常返回使用,考虑到文件存储形态可能会发生变化,存储完备数据保障数据再次迁移的可行性;
统一接口层,默认实现为系统 SP 实现,不影响业务中台,无学习成本,KV 优化和数据迁移对业务透明,手百依赖 UniKV 实现层。
对比业界对 KV 存储做相关优化,均有不同程度优势,目前在百度 App 大规模使用,接入文件 100+,优化线程数 100+,优化 TTI 约 1.2S。
锁优化
通过工具可看到锁相关信息,比如可以看到这段 Trace,具体如下图所示:
在项目中锁相关优化也是非常关键,以 ABTest 优化举例。从 Trace 文件中可以看到在 ABTest 模块中出现了较多的锁信息,读取性能差,因此对 ABTest 模块进行了全面的摸底和分析,ABTest 问题主要表现为:
通过 Trace 工具发现业务使用 ABTest 组件时出现锁同步问题,耗时 200ms+;
ABTest 组件由于历史原因,存在新老 AB 存储数据,导致首次读取耗时 2S+;
首次读取时易出现 AB 值不准确问题,导致实验数据不可信。
针对上述几个问题,对 ABTest 做了重构,框架设计如下:
核心优化主要分为初始化优化和数据读取优化:
初始化优化:优化前读取新老 AB 全量数据,文件格式为 SP;优化后读取实验 id 和实验开关缓存文件,文件格式为 JSON/PB 格式,如果缓存文件不存在,则会读取原始数据保存数据。
数据读写:优化前 synchronized 整个内存缓存,读和写均有锁保护,在高并发场景性能较差;优化后,读无锁,直接读取实验内存缓存,写无锁,由于写入有限次,在本次写入时通过内存级操作,在新申请的内存缓存中更新数据,更新全部数据后,将内存中的缓存地址更新为新申请的内存地址,解决锁带来的性能影响。
优化效果:
彻底解决业务使用 ABTest 组件锁同步问题,TTI 优化 200ms+;
兼容新老 AB 数据,且通过 JSON/PB 等数据格式验证,首次读取性能优化 95%(小米 5 机器);
解决 AB 实验数据准确性问题;
丨 4.2.3 调度框架
百度 App 启动过程复杂,涉及业务众多,各预加载任务的执行诉求各有不同。在以往业务优化中,通过预加载、预链接、预渲染等各种“预”操作,对性能指标和产品体验做了相关优化,业务完全优化掉耗时是比较理想的情况,会有较多业务依然保留“预”操作的诉求,因此需要建设一套调度框架,输出至业务方,业务可快速接入,快速解决优化业务的“预”操作带来的性能问题,达到启动性能和业务指标的平衡。本文中对此部分做简单阐述,后续也会相关文章详细介绍此部分优化。
整体框架图如下:
调度框架的核心为智能调度,输入有两部分,主要是任务和信息采集:
任务:业务可将初始化、预加载任务封装成 Task,注册至任务管理器中,任务管理器可对任务进行识别并表示,比如所属业务、依赖业务等,对任务执行情况做监控;
信息采集:主要包括机型画像(高/中/低端机)、行为画像(用户使用业务频次与时长)、场景识别(闪屏场景、端外调起场景、发起搜索场景等)、分级配置(不同机型画像不同的策略配置)。
输出为不同的调度形态,有个性化调度(不同用户运行时初始化不同业务)、分级体验调度(用户不同配置机型不同效果)、精细化调度(基于场景做调度)、分优先级延时调度(任务延迟调度支持设置优先级,调整任务顺序)和首页 UI 并行渲染技术(主要指商业闪屏和主页并行渲染),业务可根据预加载任务执行诉求快速接入调度实现优化。在任务调度中,会为特殊业务做针对性调度,赋能业务,如闪屏和首页并行渲染,提升商业请求成功率,进而商业收入。
丨 4.3 业务优化
与业务协同,在技术和产品方面深度优化启动性能,主要流程如下:
问题发现:线下发现问题主要通过工具,如 Trace 工具发现主线程耗时严重问题,主线程锁等待问题等,Hook 工具发现主线程 I/O 问题,线程创建问题等;线上发现问题主要通过线上打点和实验防劣化机制;
问题协同:与业务沟通问题及技术方案,必要时协助其分析并优化,确认上线排期;
问题优化:主要由业务来完成具体优化,如果涉及基础机制相关工作,则会由我们来主导优化,在整个优化中,调度优化为主要优化方式,业务可快速接入调度机制实现优化。
优化验证:及时跟进问题修复情况,在发版前回归问题,跟进线上优化效果,有些优化需要平衡业务指标和性能指标,如果优化不及预期需继续协同优化。
五、总结
项目持续 1 年半,共经历了 3 个阶段:夯实基础阶段、进攻突破阶段、精细管控阶段。
第一个阶段:夯实基础阶段
建设低端机标准;
建设性能指标;
建设高效性能工具,提供发现问题的能力,发现突出矛盾,跟进解决;
建设基础调度机制,为后续业务接入做准备;
第二个阶段:进攻突破阶段
通过性能工具挖掘业务相关性能问题,与业务协同优化,并提供调度机制解决业务初始化与启动性能矛盾;
深度挖掘底层组件性能优化性能;
结合核心业务场景,挖掘业务价值;
第三个阶段:精细管控阶段
建设研发全流程的防劣化机制;
通过自动化测试和自助化服务,防劣化机制自运转,控制人力成本。
深度挖掘业务场景,如目前在解决的开屏、外部调起等场景,搜索、Feed 等核心场景需要渗透,需要在做调度机制升级相关工作;
低端机优化-启动性能优化项目完成了基础设施、监控设施和业务优化方向的建设,实现可感知的用户体验提升,TTI 指标获得明显优化效果,常规冷启动 TTI Androd 优化 50%+ ,iOS 优化 40%+ ,首次启动 TTI:Android 优化 40%+,iOS 优化 30%+ 。在项目中,积极与用户增长和商业的同学合作,也均取得不错的结果,百度 App 也将优化经验、基础机制、基础工具输出至矩阵产品,助力矩阵产品的性能提升。
六、展望
启动性能是 APP 使用体验的门面,启动过程耗时较长很可能导致用户流失,启动场景作为资源竞争、卡顿情况严重的场景,各个公司团队均投入了较多精力来进行优化,且优化技术层出不穷,虽然百度 App 在此方向做了非常多的优化与尝试,但路漫漫其修远兮,百度 App 会持续深耕启动性能优化,为用户带来极致的启动体验。
与此同时,专注点不仅局限于启动场景,也会将更多精力投入到应用全生命周期、各 View 系统的流畅度建设与优化上,深入优化用户体验。
——————————END——————————
参考资料:
[1] Web 指标 FID 链接:
[2] Epic 链接:
[3] xHook 链接:
https://github.com/iqiyi/xHook
[4] Perfetto 链接:
[5] MMKV 链接:
https://github.com/Tencent/MMKV
[6] Booster 链接:
https://github.com/didi/booster
推荐阅读:
评论