啟動(dòng)性能是 APP 使用體驗(yàn)的門(mén)面茂缚,啟動(dòng)過(guò)程耗時(shí)較長(zhǎng)很可能導(dǎo)致用戶使用 APP 的興趣驟減,抖音通過(guò)對(duì)啟動(dòng)性能做劣化的 AB 實(shí)驗(yàn)也驗(yàn)證了其對(duì)于業(yè)務(wù)指標(biāo)有影響顯著硝清。抖音擁有數(shù)億的用戶辅斟,啟動(dòng)耗時(shí)幾百毫秒的增長(zhǎng)就可能帶來(lái)成千上萬(wàn)用戶的留存縮減,因此芦拿,啟動(dòng)性能的優(yōu)化成為了抖音 Android 基礎(chǔ)技術(shù)團(tuán)隊(duì)在體驗(yàn)優(yōu)化方向上的重中之重士飒。
本文基于過(guò)往對(duì)抖音 Android 客戶端做啟動(dòng)性能優(yōu)化的實(shí)戰(zhàn)經(jīng)驗(yàn)總結(jié)提煉出普適性的方法論,并將該過(guò)程中沉淀的工具加以分享蔗崎,希望能給大家?guī)?lái)一些新的思考酵幕。
抖音 Android 性能優(yōu)化系列往期文章回顧:新一代全能型性能分析工具 Rhea
帶著問(wèn)題出發(fā)
假如你要負(fù)責(zé)優(yōu)化抖音的啟動(dòng)性能,你會(huì)怎樣去規(guī)劃整體的優(yōu)化方案缓苛?你可能會(huì)一下子想到很多方面的細(xì)節(jié)點(diǎn)芳撒,比如:要優(yōu)化主線程耗時(shí)、要減少布局層級(jí)未桥、要對(duì)某些啟動(dòng)任務(wù)做按需加載或預(yù)加載番官、要避免主線程 IO、要對(duì)線程使用進(jìn)行優(yōu)化钢属、還要有分析工具幫助定位性能問(wèn)題等……
然而徘熔,該如何系統(tǒng)性地把這些細(xì)碎點(diǎn)組織起來(lái)并按照一定的章法來(lái)落地啟動(dòng)優(yōu)化呢?此時(shí)淆党,需要我們?cè)诰唧w細(xì)節(jié)點(diǎn)之上有進(jìn)一步的問(wèn)題分解與深入思考酷师,最終形成一套完整的方法論讶凉,不僅能覆蓋所有細(xì)節(jié)點(diǎn),還能切實(shí)指導(dǎo)在實(shí)戰(zhàn)中達(dá)成啟動(dòng)優(yōu)化的效果山孔。切實(shí)有效的方法論必然是從實(shí)戰(zhàn)中經(jīng)過(guò)千錘百煉才能形成的懂讯,而抖音龐大的用戶基數(shù)又進(jìn)一步保障了方法論的可行性與普適性。那么接下來(lái)讓我們帶著前述問(wèn)題來(lái)看抖音的啟動(dòng)優(yōu)化方法論是怎樣的又是如何應(yīng)用于實(shí)戰(zhàn)之中的台颠。
啟動(dòng)優(yōu)化方法論
抖音的啟動(dòng)性能優(yōu)化方法論分為五部分褐望,分別是:理論分析、現(xiàn)狀分析串前、啟動(dòng)性能優(yōu)化瘫里、線上驗(yàn)證與防劣化。
這五部分間存在明顯的先后順序荡碾,又能閉環(huán)達(dá)成可持續(xù)的啟動(dòng)性能優(yōu)化谨读,下面將對(duì)這五部分做詳細(xì)闡述:
理論分析
理論分析放在最先是為了從一開(kāi)始就避免讓視野受到限制,很多同學(xué)往往一開(kāi)始接手啟動(dòng)優(yōu)化就容易陷入對(duì)各種現(xiàn)狀細(xì)節(jié)的分析坛吁,拘泥于片面的潛在可優(yōu)化點(diǎn)劳殖,這樣就難以做到對(duì)全局和優(yōu)先級(jí)的把控,所以拨脉,我們應(yīng)該首先跳出現(xiàn)狀哆姻,從更加全局的視角來(lái)思考整體優(yōu)化的目標(biāo)和策略。這里可以利用特斯拉創(chuàng)始人——埃隆·馬斯克所推崇的“第一性原理”思考法:
“通過(guò)第一性原理玫膀,把事情升華到最根本的真理矛缨,然后從最核心處開(kāi)始推理〈移”
基于此,我們?cè)谧鰡?dòng)優(yōu)化的理論分析時(shí)可以從更本源的角度出發(fā)做到全局思考誉简,比如抖音會(huì)做從進(jìn)程創(chuàng)建到頁(yè)面展示的全啟動(dòng)路徑分階段耗時(shí)分析碉就、還會(huì)按照消耗的系統(tǒng)資源類型做耗時(shí)成因分析,通過(guò)這種極致的耗時(shí)分析可以帶來(lái)極致的優(yōu)化策略闷串,此外瓮钥,從全路徑出發(fā)還能夠發(fā)現(xiàn)容易忽視的問(wèn)題、探索優(yōu)化的極限烹吵。
現(xiàn)狀分析
在完成理論分析后碉熄,我們基本具備了全局的視角,并且也大致清楚了整體的優(yōu)化目標(biāo)和策略肋拔,接下來(lái)就要基于此來(lái)做現(xiàn)狀分析從而明晰實(shí)現(xiàn)目標(biāo)的具體路徑:
- 首先使用 profile 工具對(duì)可優(yōu)化點(diǎn)進(jìn)行摸底:其實(shí)不合理的高耗時(shí)點(diǎn)就是潛在的優(yōu)化點(diǎn)锈津,并能按照前述的理論分析歸入一個(gè)或多個(gè)耗時(shí)成因中;
- 然后結(jié)合線上的指標(biāo)數(shù)據(jù)確定最終優(yōu)化方向:線下摸底的潛在優(yōu)化點(diǎn)要結(jié)合其線上打點(diǎn)確認(rèn)是否為普遍耗時(shí)凉蜂,再根據(jù)耗時(shí)成因明確大致的優(yōu)化思路琼梆、實(shí)施成本和預(yù)估收益性誉。
在這部分需要尤其注意三點(diǎn):優(yōu)質(zhì)的 profile 工具(這里推薦使用同樣來(lái)自基礎(chǔ)技術(shù)團(tuán)隊(duì)的“新一代全能型性能分析工具”)、線下 trace 結(jié)合線上監(jiān)控綜合分析茎杂、根據(jù)投入產(chǎn)出比評(píng)估實(shí)施優(yōu)先級(jí)错览,這三點(diǎn)是保障切實(shí)有效取得啟動(dòng)優(yōu)化收益的關(guān)鍵。
啟動(dòng)優(yōu)化
在完成了理論和現(xiàn)狀分析后煌往,就可以根據(jù)規(guī)劃的路徑來(lái)實(shí)施具體的啟動(dòng)優(yōu)化項(xiàng)了倾哺。在實(shí)施過(guò)程中,主要考慮主線程優(yōu)化刽脖、后臺(tái)線程優(yōu)化和全局優(yōu)化三個(gè)維度:
- 主線程耗時(shí)優(yōu)化需要在啟動(dòng)全路徑各階段中細(xì)化具體的耗時(shí)成因羞海,如:CPU Time、CPU Schedule曾棕、IO wait扣猫、Lock wait 等,完成耗時(shí)歸因后可以使用逐步升級(jí)的優(yōu)化策略來(lái)逐個(gè)擊破:對(duì)于首屏所必須的耗時(shí)邏輯做正面優(yōu)化(可使用縮減耗時(shí)邏輯翘地、異步并發(fā)申尤、延遲加載等手段)、對(duì)于非首屏必須的耗時(shí)邏輯做按需加載(需要架構(gòu)優(yōu)化的基礎(chǔ))衙耕、對(duì)于優(yōu)化后仍存在耗時(shí)的邏輯嘗試做業(yè)務(wù)降級(jí)(大都有損需評(píng)估全局收益)昧穿;
- 后臺(tái)線程優(yōu)化策略與主線程類似,在此基礎(chǔ)上還可以實(shí)施后臺(tái)任務(wù)縮減橙喘、線程收斂时鸵、開(kāi)啟多進(jìn)程等優(yōu)化措施;此外厅瞎,主線程和后臺(tái)線程均存在較多啟動(dòng)任務(wù)且彼此間可能存在關(guān)聯(lián)饰潜,因此,可以對(duì)全局的啟動(dòng)任務(wù)做依賴關(guān)系梳理并實(shí)施精細(xì)化的任務(wù)重排和簸,旨在減少依賴任務(wù)間的等待耗時(shí)彭雾;
- 全局優(yōu)化主要是指業(yè)務(wù)無(wú)關(guān)的通用的全局優(yōu)化策略,如虛擬機(jī)層面或 IO 層面的優(yōu)化等锁保。
線上驗(yàn)證
在完成了具體的優(yōu)化項(xiàng)施工后薯酝,就來(lái)到了線上驗(yàn)證大盤(pán)收益的階段。這個(gè)階段有三點(diǎn)需要注意:
- 線下的優(yōu)化一定要有線上的指標(biāo)反饋爽柒,線下的優(yōu)化項(xiàng)因?yàn)樵O(shè)備或操作習(xí)慣差異往往難以評(píng)估是否具備普遍影響吴菠,只有當(dāng)相應(yīng)的線上指標(biāo)取得正面反饋后才能驗(yàn)證拿到了有效的優(yōu)化收益;
- 線上指標(biāo)需要結(jié)合均值與分位值綜合來(lái)評(píng)估浩村,只關(guān)注啟動(dòng)耗時(shí)的均值往往會(huì)掩蓋低分位設(shè)備的現(xiàn)狀做葵,這部分設(shè)備可能占比不高,對(duì)均值影響有限心墅,但抖音龐大的用戶基數(shù)乘以該比例仍舊是不小的數(shù)量蜂挪,為了保障該部分用戶的啟動(dòng)性能體驗(yàn)重挑,抖音一般會(huì)分 50%、70%棠涮、90%三個(gè)分位值來(lái)評(píng)估指標(biāo)谬哀;
- 在驗(yàn)證收益時(shí)通過(guò) AB 實(shí)驗(yàn)達(dá)成,這樣做不僅能控制變量確保優(yōu)化項(xiàng)的嚴(yán)格有效严肪,還能借此來(lái)觀察性能優(yōu)化所帶來(lái)的業(yè)務(wù)指標(biāo)收益史煎,這些都可以作為規(guī)劃后續(xù)啟動(dòng)優(yōu)化方向的參考指導(dǎo)。
防劣化
在線上驗(yàn)證優(yōu)化措施取得切實(shí)收益后驳糯,并不是萬(wàn)事大吉了篇梭,持續(xù)保持住優(yōu)化效果才算完整達(dá)成了啟動(dòng)性能優(yōu)化的目的。其實(shí)不僅是啟動(dòng)優(yōu)化酝枢,整個(gè)性能優(yōu)化領(lǐng)域都是圍繞著“攻”和“守”來(lái)展開(kāi)的恬偷,“攻”即為前述的分析與優(yōu)化,而“守”則是防止劣化帘睦,在防劣化方面大家往往不會(huì)像優(yōu)化的方面那么重視袍患,但實(shí)際上能防止劣化是可持續(xù)取得優(yōu)化效果的前提(否則新的優(yōu)化效果會(huì)用于彌補(bǔ)劣化甚至入不敷出),并且防劣化相比于優(yōu)化是更能持久有益的竣付。
抖音啟動(dòng)性能防劣化的進(jìn)程分為了三個(gè)時(shí)期诡延,不同時(shí)期有不同的表現(xiàn)與應(yīng)對(duì)手段,這很可能是大多數(shù) APP 優(yōu)化啟動(dòng)性能都要經(jīng)歷的古胆,這里提煉出來(lái)以供參考:
- 快速下降期:此時(shí)一般位于啟動(dòng)優(yōu)化的初始階段肆良,優(yōu)化空間很大,伴隨有小幅度的劣化但往往都能被更大幅度的優(yōu)化抵消且還仍有收益逸绎,這時(shí)應(yīng)該抓大放小惹恃,按照更高投入產(chǎn)出比的策略重點(diǎn)推進(jìn)優(yōu)化,同時(shí)也抽出少部分精力治理修復(fù)成本低的劣化棺牧。
- 瓶頸期:到了該時(shí)期絕大部分優(yōu)化收益已經(jīng)拿到巫糙,想進(jìn)一步做到優(yōu)化往往需要投入更多成本,且優(yōu)化幅度有限陨帆,整體的投入產(chǎn)出比不高曲秉,同期還會(huì)伴隨有中小幅的劣化采蚀,此時(shí)需要建立完善的線上線下監(jiān)控體系疲牵,及時(shí)發(fā)現(xiàn)并修復(fù)劣化,此外還要通過(guò)架構(gòu)改造從源頭上限制劣化的發(fā)生榆鼠,綜合保障優(yōu)化的收益不會(huì)被劣化抵消纲爸。
- 劣化期:這個(gè)時(shí)期往往出現(xiàn)在年關(guān)或重要節(jié)日期間,這類時(shí)間點(diǎn)往往有重要且緊急的活動(dòng)項(xiàng)目上線妆够,眾多關(guān)聯(lián)方面均要為其開(kāi)綠燈识啦,啟動(dòng)性能指標(biāo)也不例外负蚊,為了保障活動(dòng)效果可能要加入若干耗時(shí)的主線程啟動(dòng)任務(wù),所帶來(lái)的的劣化幅度往往比較大颓哮,此時(shí)需要對(duì)齊預(yù)期并在活動(dòng)結(jié)束后及時(shí)修復(fù)家妆。
啟動(dòng)優(yōu)化方法論的應(yīng)用實(shí)踐
古人云“紙上得來(lái)終覺(jué)淺,絕知此事要躬行”冕茅,前述的方法論講得再詳細(xì)再透徹也會(huì)與實(shí)際的落地存在隔閡伤极,為了做到真正的學(xué)以致用,下文將細(xì)致講解如何將啟動(dòng)優(yōu)化方法論應(yīng)用于實(shí)踐之中姨伤。
理論分析的實(shí)踐
抖音在理論分析部分會(huì)對(duì)啟動(dòng)流程分別作全路徑分析和耗時(shí)成因分析哨坪,前者用于發(fā)現(xiàn)全路徑各個(gè)階段的潛在耗時(shí)點(diǎn)避免疏漏,后者用于系統(tǒng)性地將各個(gè)耗時(shí)點(diǎn)歸因從而引導(dǎo)我們找尋優(yōu)化思路乍楚,關(guān)于這兩部分的具體實(shí)踐如下: 啟動(dòng)性能全路徑分析:抖音的啟動(dòng)路徑和大多數(shù) APP 類似当编,整體分為兩大階段和兩個(gè)間隙,它們按時(shí)間順序排布為:Application 階段徒溪、handle message 間隙忿偷、Activity 階段和數(shù)據(jù)加載間隙,全路徑各部分細(xì)分涵蓋的內(nèi)容如下圖所示:
APP 進(jìn)程由 zygote 進(jìn)程 fork 出來(lái)后會(huì)執(zhí)行 ActivityThread 的 main 方法词渤,該方法最終觸發(fā)執(zhí)行bindApplication牵舱,這也是 Application 階段的起點(diǎn);然后是我們?cè)趹?yīng)用中能觸達(dá)到的attachBaseContext階段缺虐,4.x 的機(jī)型在該階段具有較長(zhǎng)的 MultiDex 耗時(shí)可以做針對(duì)性優(yōu)化(可參考“抖音 BoostMultiDex 優(yōu)化實(shí)踐”)芜壁,本階段也是最早的預(yù)加載時(shí)機(jī);接下來(lái)是installProvider階段高氮,很多三方 sdk 借助該時(shí)機(jī)來(lái)做初始化操作慧妄,很可能導(dǎo)致啟動(dòng)耗時(shí)的不可控情形,需要按具體 case 優(yōu)化剪芍;此后就到了 Application 的onCreate階段塞淹,這里有很多三方庫(kù)和業(yè)務(wù)的初始化操作,是通過(guò)異步罪裹、按需饱普、預(yù)加載等手段做優(yōu)化的主要時(shí)機(jī),它也是 Application 階段的末尾状共。
在Application 階段和 Activity 階段之間往往會(huì)不可避免地被插入很多 post 到主線程的消息及相應(yīng)待執(zhí)行任務(wù)套耕,這是拉長(zhǎng)啟動(dòng)耗時(shí)的另一不可控問(wèn)題點(diǎn),需要加以監(jiān)控治理或通過(guò)消息調(diào)度優(yōu)化來(lái)盡量減小此間隙峡继。
在來(lái)到 Activity 階段后冯袍,首先經(jīng)歷的是其onCreate生命周期,這里涵蓋了首屏業(yè)務(wù)優(yōu)化的主要場(chǎng)景也是開(kāi)啟異步并發(fā)的主要時(shí)機(jī),在其中有個(gè)重要的 setContentView 方法會(huì)觸發(fā) DecorView 的 install康愤,可嘗試對(duì) DecorView 的構(gòu)建進(jìn)行預(yù)加載儡循;后續(xù)自然來(lái)到View 構(gòu)建的階段,該階段在抖音上相當(dāng)耗時(shí)征冷,可采用異步 Inflate 配合 X2C(編譯期將 xml 布局轉(zhuǎn)代碼)并提升相應(yīng)異步線程優(yōu)先級(jí)的方法綜合優(yōu)化择膝;再來(lái)到View 的整體渲染階段,涵蓋 measure检激、layout调榄、draw 三部分,這里可嘗試從層級(jí)呵扛、布局每庆、渲染上取得優(yōu)化收益。
最后是首屏數(shù)據(jù)加載階段今穿,這部分涵蓋非常多數(shù)據(jù)相關(guān)的操作缤灵,也需要綜合性優(yōu)化,可嘗試預(yù)加載蓝晒、緩存或網(wǎng)絡(luò)優(yōu)先級(jí)調(diào)度等手段腮出。
此外,針對(duì)全路徑所有階段還可以實(shí)施通用性的優(yōu)化項(xiàng)芝薇,如:?jiǎn)?dòng)任務(wù)調(diào)度框架胚嘲、類重排、IO 預(yù)加載洛二、全局通用性框架優(yōu)化等馋劈。
啟動(dòng)耗時(shí)成因分析:所有的耗時(shí)均因代碼運(yùn)行時(shí)不合理地消耗系統(tǒng)資源產(chǎn)生,而不合理的耗時(shí)點(diǎn)正是需要做歸因分析之處晾嘶。抖音按照不合理耗時(shí)點(diǎn)消耗的主要系統(tǒng)資源類型劃分出五大成因妓雾,分別是:CPU Time、CPU Schedule垒迂、IO Wait械姻、Lock Wait 和 IPC,下面分別對(duì)各成因進(jìn)行剖析:
- CPU Time 指占用 CPU 進(jìn)行計(jì)算所花費(fèi)的時(shí)間絕對(duì)值机断,中斷楷拳、掛起、休眠等行為是不會(huì)增加 CPU Time 的吏奸,所以因 CPU Time 開(kāi)銷占比高導(dǎo)致的不合理耗時(shí)點(diǎn)往往是邏輯本身復(fù)雜冗長(zhǎng)需要消耗較多 cpu 時(shí)間片才能處理完欢揖。比較常見(jiàn)的高 CPU 占用是循環(huán),比如抖音啟動(dòng)時(shí)遇到過(guò)一個(gè) so 加載耗時(shí)苦丁,最后定位原因是在解壓 so 的時(shí)候浸颓,遍歷 ZipEntry 的次數(shù)過(guò)多導(dǎo)致物臂,一個(gè)可行的優(yōu)化策略就是可以把 so 所在的 ZipEntry 提前旺拉,遍歷完 so 的 ZipEntry 之后可以提前中止遍歷产上,而不需要遍歷剩下的無(wú)效 ZipEntry。除循環(huán)之外蛾狗,反射也是導(dǎo)致 CPU Time 的重要原因晋涣,像在序列化/反序列化、View Inflate 時(shí)沉桌,都有大量的反射操作谢鹊,反射的耗時(shí)主要是字符串去查找 Method 或者 Field,這個(gè)優(yōu)化策略也可以考慮提前查找 Method 和 Field 緩存起來(lái)留凭,或者是通過(guò)內(nèi)聯(lián)來(lái)降低 Field 數(shù)量等佃扼。另外一個(gè)常見(jiàn)的 CPU 耗時(shí)是類加載,類的加載過(guò)程包括:Load蔼夜,從 Dex 文件里讀取類的信息兼耀,可通過(guò)類重排優(yōu)化;Verify求冷,驗(yàn)證指令是否合法等瘤运,通過(guò)關(guān)掉 Class Verify 可以優(yōu)化該過(guò)程,同時(shí)高版本的 vdex 也是為了優(yōu)化 verify 過(guò)程而設(shè)計(jì)匠题,在 dex2oat 的時(shí)候做 verify拯坟,verify 之后的結(jié)果保存成 vdex,后續(xù)只需要加載 vdex韭山;Link郁季,給 Field、Method 分配內(nèi)存钱磅,按照名字排序以方便后續(xù)反射的時(shí)候查找 Field巩踏、Method 等,這個(gè)過(guò)程的優(yōu)化续搀,art 虛擬機(jī)采用了 ImageSpace 的方案進(jìn)行了優(yōu)化塞琼,將 Link 后的內(nèi)存保存為 image 文件,后續(xù)可以直接 load 這個(gè) image 文件禁舷,省去了 Link 過(guò)程彪杉;Init,類的初始化牵咙。
- CPU Schedule 在分析時(shí)主要針對(duì)主線程派近,是指主線程處于可執(zhí)行狀態(tài)但獲取不到 cpu 時(shí)間片,這類耗時(shí)可能和線程調(diào)度等有關(guān)洁桌,最終導(dǎo)致分配給主線程的 cpu 時(shí)間片不足以及時(shí)處理完其內(nèi)任務(wù)渴丸。由于主線程的線程優(yōu)先級(jí)比其他線程的優(yōu)先級(jí)要高很多,通常影響并不大,事實(shí)上抖音做了線上用戶的啟動(dòng)耗時(shí)統(tǒng)計(jì)谱轨,這部分的耗時(shí)占比也是不大的戒幔。不過(guò)有一個(gè)場(chǎng)景需要關(guān)注,就是渲染土童,渲染是需要 RenderThread 提交 GPU 的渲染命令诗茎,而 RenderThread 并沒(méi)有主線程那么高的優(yōu)先級(jí),因此比較容易受 CPU 的負(fù)載的影響献汗,導(dǎo)致渲染耗時(shí)敢订,這個(gè)對(duì)于啟動(dòng)來(lái)說(shuō)影響并不算大,啟動(dòng)只有一次首頁(yè)的渲染罢吃,占整體時(shí)間的比例不算大楚午,但對(duì)于流暢度的影響就會(huì)比較大。這類耗時(shí)的優(yōu)化主要還是從降低 CPU 的負(fù)載的角度考慮尿招,比如業(yè)務(wù)降級(jí)醒叁、業(yè)務(wù)打散等手段。抖音還通過(guò)對(duì) RenderThread 優(yōu)先級(jí)的提升優(yōu)化泊业,拿到了不錯(cuò)的收益把沼。
- IO Wait 指發(fā)生了 IO 操作需要等待 IO 返回結(jié)果,這類耗時(shí)可能發(fā)生在讀取資源和文件吁伺,類加載饮睬,甚至在內(nèi)存不足時(shí)的 PageFault 都會(huì)導(dǎo)致 IO Wait。Resources 的相關(guān)的操作耗時(shí)篮奄,主要是需要從 apk 里讀取資源文件捆愁,優(yōu)化策略可以有預(yù)加載、資源重排窟却、資源異步加載等昼丑。類加載的 IO Wait 和 Resources 類似,也可以通過(guò)類的重排夸赫、預(yù)加載等優(yōu)化方案菩帝。文件讀寫(xiě)導(dǎo)致的 IO Wait 又分為業(yè)務(wù)文件和系統(tǒng)文件,業(yè)務(wù)文件指業(yè)務(wù)邏輯的讀寫(xiě)文件茬腿,一般都可以通過(guò)異步來(lái)解決呼奢,而系統(tǒng)文件的例子是 dex 的讀寫(xiě),抖音的 IO Wait 很大一塊是它貢獻(xiàn)的切平,目前的思路還是做 dex 的重排和 IO 的預(yù)讀來(lái)嘗試優(yōu)化握础。
- Lock Wait 也是主要針對(duì)主線程,指其處于等鎖狀態(tài)悴品,等待被其他線程喚醒或自己超時(shí)喚醒禀综,導(dǎo)致這類耗時(shí)的問(wèn)題種類多樣简烘,大體也是可以分為業(yè)務(wù)鎖和系統(tǒng)鎖,業(yè)務(wù)鎖主要是被主線程等待的業(yè)務(wù)邏輯未能及時(shí)處理完定枷,優(yōu)化思路一般是移除主線程的鎖等待邏輯或者加快被等待的業(yè)務(wù)邏輯的執(zhí)行速度孤澎。系統(tǒng)鎖主要有:String InternTable Lock,ClassLinker Lock依鸥,GC Wait Lock 等,目前抖音正在嘗試優(yōu)化這幾類的鎖耗時(shí)悼沈。
- IPC 指進(jìn)程間通信贱迟,操作系統(tǒng)大都含有相應(yīng)的機(jī)制,Android 中所特有的 IPC 機(jī)制是 Binder絮供,由于進(jìn)行 IPC 調(diào)用往往需要等待通信結(jié)果本質(zhì)上這也算是一種 Lock Wait衣吠,但 Android 特有 Binder 機(jī)制所以單獨(dú)列出,這類耗時(shí)可采用減少或替代 Binder 調(diào)用等手段來(lái)優(yōu)化壤靶。
綜合前述的五大耗時(shí)成因缚俏,這里舉一個(gè)分析啟動(dòng)階段 UI 耗時(shí)成因的例子作為實(shí)踐參考,根據(jù) UI 界面的生命周期(一般劃分)——UI 構(gòu)建贮乳、數(shù)據(jù)綁定忧换、View 顯示三個(gè)階段分別進(jìn)行分析:
- 在UI 構(gòu)建階段中首先要對(duì)界面布局的 xml 文件進(jìn)行解析,這會(huì)導(dǎo)致 IO Wait 耗時(shí)向拆,在接下來(lái)要解析 xml 文件中的 TagName 從而獲取對(duì)應(yīng) View 的 class 會(huì)用到反射亚茬、創(chuàng)建各子 View 實(shí)例并生成 View 樹(shù)又會(huì)用到循環(huán)遞歸,兩部分都會(huì)增加 CPU Time 的開(kāi)銷浓恳。
- 然后是數(shù)據(jù)綁定階段刹缝,該階段主要分兩部分,一部分是對(duì)數(shù)據(jù)做請(qǐng)求颈将、解析梢夯、適配,另一是部分是將適配好的數(shù)據(jù)填充進(jìn) UI 中晴圾,前一部分往往會(huì)涉及到 Json 解析成 Data Class 實(shí)例颂砸,這里就可能涉及反射、循環(huán)遍歷嵌套的數(shù)據(jù)類結(jié)構(gòu)等增加 CPU Time 的操作死姚。
- 最后是View 顯示階段沾凄,常見(jiàn)的 measure、layout知允、draw 三大渲染 View 的步驟就在其中撒蟀,它們同樣會(huì)產(chǎn)生遞歸遍歷父子 View 的耗時(shí),此外這里還涉及將應(yīng)用層計(jì)算好的渲染 View 的數(shù)據(jù)傳遞給系統(tǒng)層做最終的像素點(diǎn)排布温鸽,那么必然又會(huì)產(chǎn)生 IPC 耗時(shí)保屯。
從這個(gè)例子可見(jiàn)即使再?gòu)?fù)雜的場(chǎng)景只要我們進(jìn)行細(xì)粒度的分析手负,都能將耗時(shí)點(diǎn)歸入前述某一成因中。
現(xiàn)狀分析的實(shí)踐
如前文方法論所述姑尺,現(xiàn)狀分析包括線下 Profile 數(shù)據(jù)與線上監(jiān)控?cái)?shù)據(jù)的對(duì)照分析竟终,綜合這兩部分可以明確切實(shí)影響大盤(pán)啟動(dòng)性能的普遍耗時(shí)點(diǎn),從而確保要做的優(yōu)化項(xiàng)是行之有效的切蟋。下面分別講述這兩部分?jǐn)?shù)據(jù)的分析實(shí)踐:
線下 Profile 數(shù)據(jù)分析:Profile 主要是指使用性能探測(cè)工具抓取應(yīng)用啟動(dòng)路徑各階段的耗時(shí)和系統(tǒng)資源消耗情況统捶,常見(jiàn)的開(kāi)源 Profile 工具有 TraceView、Systrace柄粹、Android Profiler 等喘鸟,這些工具各有優(yōu)勢(shì)但均不能完全滿足抖音做線下 Profile 的需求(詳見(jiàn)后文“啟動(dòng)性能優(yōu)化工具”部分的講解),為此驻右,抖音自研了“新一代全能型性能分析工具 RheaTrace”滿足了需求什黑。通過(guò)該工具我們可以在線下抓取整個(gè)啟動(dòng)路徑的 Trace 文件,其整體樣式與 Systrace 一致堪夭,但是涵蓋了更多的信息點(diǎn)愕把,一個(gè)樣例 Trace 文件如下圖所示:
這里需要注意抓取 Trace 一定要基于 release 包,debug 包中往往涵蓋諸多調(diào)試邏輯可能影響啟動(dòng)性能森爽,導(dǎo)致 profile 數(shù)據(jù)與實(shí)際使用情形存在偏差恨豁。在查看 profile 數(shù)據(jù)時(shí),首要觀察主線程爬迟,尋找其中不符合預(yù)期的耗時(shí)方法圣絮,抖音將主線程耗時(shí)在 5ms 以上的方法均認(rèn)定為不符合預(yù)期;然后在所有不符合預(yù)期的方法中尋找 Top n 的耗時(shí)點(diǎn)雕旨,逐個(gè)分析耗時(shí)原因扮匠、尋找突破口;耗時(shí)原因需要結(jié)合方法實(shí)現(xiàn)邏輯以及諸多運(yùn)行時(shí)信息綜合分析(這里可以參考 Google 官方文檔“瀏覽 Systrace 報(bào)告”)凡涩,需要關(guān)注的運(yùn)行時(shí)信息有方法執(zhí)行時(shí)段對(duì)應(yīng)的 CPU 負(fù)載棒搜、線程狀態(tài)的顏色標(biāo)識(shí)值、鎖信息活箕、IO 耗時(shí)力麸、Binder 調(diào)用耗時(shí)等,根據(jù)這些信息判定引起方法耗時(shí)的主要原因育韩,再結(jié)合理論分析中不同階段克蚂、不同系統(tǒng)資源類型探尋優(yōu)化手段。
線上監(jiān)控?cái)?shù)據(jù)分析:這部分?jǐn)?shù)據(jù)的分析主要是用作參照和補(bǔ)充筋讨,參照是指線下 Profile 數(shù)據(jù)分析出的耗時(shí)點(diǎn)要對(duì)照線上數(shù)據(jù)確認(rèn)其在大盤(pán)中存在普遍耗時(shí)埃叭,補(bǔ)充是指線下 Profile 數(shù)據(jù)未能復(fù)現(xiàn)的耗時(shí)點(diǎn)可能存在于線上大盤(pán)中,這部分漏掉的耗時(shí)點(diǎn)需要在線下嘗試復(fù)現(xiàn)悉罕、歸因后實(shí)施優(yōu)化赤屋。這里有個(gè)很重要的點(diǎn)是:該如何對(duì)線上的啟動(dòng)性能指標(biāo)做監(jiān)控立镶,這是保障線上數(shù)據(jù)能真實(shí)反映用戶體驗(yàn)并且與 QA(做競(jìng)品測(cè)試等)和業(yè)務(wù)方(判斷業(yè)務(wù)需求是否影響啟動(dòng)性能等)達(dá)成一致的前提,下面將對(duì)這部分做詳細(xì)闡述类早,分為啟動(dòng)性能指標(biāo)的定義媚媒、統(tǒng)計(jì)和校準(zhǔn)三部分:
-
啟動(dòng)性能指標(biāo)定義:?jiǎn)?dòng)指標(biāo)定義主要在于如何確定啟動(dòng)路徑的起點(diǎn)與終點(diǎn)。起點(diǎn)的備選項(xiàng)有下圖中的三個(gè)點(diǎn)以及 Application 的 attachBaseContext 方法:
- 下圖中“點(diǎn)擊圖標(biāo)”后的/proc/self/stats starttime 是內(nèi)核中記錄的 App 啟動(dòng)時(shí)間點(diǎn)涩僻,該數(shù)據(jù)的 Android 版本兼容性良好也比較貼合真實(shí)情況缭召,可以作為備選;
- 接下來(lái)的 Process.getStartElapsedRealTime 是 Framework 中記錄的 APP 進(jìn)程創(chuàng)建起點(diǎn)逆日,該 API 是 Android N 起才提供的兼容性較差嵌巷;
- 再往后是 Application 的構(gòu)造函數(shù),按照 Android 官方生命周期式的開(kāi)發(fā)模式通常不會(huì)往 Application 的構(gòu)造函數(shù)中加邏輯屏富,所以不建議在這里記錄起點(diǎn)晴竞;
- 最后是大家熟悉的 attachBaseContext 方法也是 Application 生命周期中非常早的一個(gè)點(diǎn)蛙卤,可以在這里記錄啟動(dòng)點(diǎn)狠半,雖然可能和真實(shí)情況有小幅差距,但能夠起到基本的對(duì)照效應(yīng)并且處于 APP 邏輯可以干預(yù)的范圍內(nèi)颤难,抖音的啟動(dòng)路徑起點(diǎn)選定的正是此處神年,此外也可以結(jié)合前述的內(nèi)核中啟動(dòng)時(shí)刻綜合觀測(cè)。
- 而關(guān)于終點(diǎn)的定義同樣有幾個(gè)備選項(xiàng):Activity-onResume行嗤、Activity-onWindowFocusChanged已日、View-dispatchDraw 和 DecorView-post:
- 少數(shù) APP 在選定冷啟階段終點(diǎn)時(shí)可能會(huì)選擇首頁(yè) Activity 的 onResume 時(shí)機(jī),但此時(shí)整個(gè) Activity 還不是完全可見(jiàn)栅屏,與用戶感受到的冷啟階段結(jié)束時(shí)刻有一定的差距飘千;
- 基于上述原因,更多 APP 會(huì)選擇首頁(yè) Activity 的 onWindowFocusChanged 時(shí)機(jī)栈雳,抖音也是選擇的此時(shí)機(jī)作為冷啟過(guò)程的終點(diǎn)护奈,此時(shí)首頁(yè) Activity 已經(jīng)可見(jiàn)但其內(nèi)部的 view 還不可見(jiàn),對(duì)于用戶側(cè)已經(jīng)可以看見(jiàn)首頁(yè)背景哥纫,即認(rèn)為冷啟階段到此結(jié)束霉旗,后續(xù)的首頁(yè)內(nèi) View 繪制歸入首刷過(guò)程中;
- dispatchDraw 從 View 可見(jiàn)這個(gè)角度講應(yīng)該是比較接近用戶感受的蛀骇,但其受業(yè)務(wù)改動(dòng)影響較大厌秒,不利于把控冷啟時(shí)間及維護(hù);
- 最后是通過(guò) DecorView 在 attachToWindow 前 post 一個(gè) runnable 來(lái)打點(diǎn)的方式擅憔,該方式可以保障在業(yè)務(wù) View 完成渲染后做打點(diǎn)鸵闪,但該方式可能會(huì)受業(yè)務(wù)同學(xué)做懶加載在打點(diǎn)前插入邏輯的影響,因此抖音的冷啟終點(diǎn)也未選用該時(shí)機(jī)暑诸。
- 啟動(dòng)性能指標(biāo)統(tǒng)計(jì):在統(tǒng)計(jì)性能指標(biāo)時(shí)有個(gè)關(guān)鍵點(diǎn)往往被大家忽略岛马,就是分位值的概念棉姐,由于平均值相對(duì)更通俗易懂且對(duì)大盤(pán)突發(fā)問(wèn)題敏感往往作為首要統(tǒng)計(jì)指標(biāo)被關(guān)注,但其存在波動(dòng)較大不利于大盤(pán)監(jiān)控以及難以體現(xiàn)不同分位機(jī)型啟動(dòng)性能差異的問(wèn)題啦逆,而分位值更有利于全面監(jiān)控且各分位波動(dòng)相對(duì)較小伞矩,此外對(duì)于低端機(jī)的性能問(wèn)題能夠更好地顯現(xiàn)出來(lái),有助于做專項(xiàng)優(yōu)化夏志,在優(yōu)化抖音的啟動(dòng)性能時(shí)我們會(huì)重點(diǎn)關(guān)注 50 分位和 90 分的性能指標(biāo)乃坤,不過(guò)分位值也存在一些缺點(diǎn),比如:概念理解起來(lái)相對(duì)復(fù)雜沟蔑、個(gè)別 bad case 分散到各分位不容易體現(xiàn)出來(lái)等湿诊,因此比較好的實(shí)踐是:日常優(yōu)化主要統(tǒng)計(jì)分位值,平均值作為輔助完善監(jiān)控體系瘦材。
- 啟動(dòng)性能指標(biāo)校準(zhǔn):由于啟動(dòng)路徑往往比較復(fù)雜厅须,因此添加了啟動(dòng)性能埋點(diǎn)后還需要額外的校準(zhǔn),總的原則是需要保障指標(biāo)數(shù)據(jù)能切實(shí)反映大盤(pán)用戶情形食棕。在添加客戶端埋點(diǎn)時(shí)最好是先梳理再分主路徑和重點(diǎn) case 分別打點(diǎn)喇澡,此外還要對(duì)若干異常 case 的數(shù)據(jù)進(jìn)行剔除或分類避免污染打點(diǎn)數(shù)據(jù)翼岁,比如抖音在添加啟動(dòng)時(shí)間打點(diǎn)時(shí)就會(huì)對(duì)開(kāi)屏廣告检访、甭拊危活進(jìn)程、push 拉起憔儿、deeplink 拉起忆植、啟動(dòng)期間退后臺(tái)、新用戶啟動(dòng)等場(chǎng)景進(jìn)行過(guò)濾或分開(kāi)統(tǒng)計(jì)谒臼。
啟動(dòng)優(yōu)化的實(shí)踐
在做完理論分析與現(xiàn)狀分析后朝刊,我們基本對(duì)全局待優(yōu)化點(diǎn)及其大致優(yōu)化方向會(huì)產(chǎn)生整體的認(rèn)知,在開(kāi)始落地各個(gè)優(yōu)化措施之前還有很重要但往往會(huì)被忽略的一步——按優(yōu)先級(jí)排布優(yōu)化項(xiàng)蜈缤、制定整體優(yōu)化方案拾氓,這一步在很大程度上制約著后續(xù)啟動(dòng)優(yōu)化的收益預(yù)期與進(jìn)展把控,這兩點(diǎn)對(duì)于按時(shí)達(dá)成啟動(dòng)優(yōu)化的終極目標(biāo)都至關(guān)重要劫樟。前述中提及了對(duì)“優(yōu)先級(jí)”的把控痪枫,這點(diǎn)是制定整體優(yōu)化方案的重中之重。
從抖音啟動(dòng)優(yōu)化實(shí)踐總結(jié)來(lái)看比較好的優(yōu)先級(jí)策略是按照“投入產(chǎn)出比”來(lái)排布優(yōu)化項(xiàng)叠艳,顧名思義:投入人力越少但優(yōu)化幅度越大的優(yōu)化項(xiàng)越應(yīng)該排在前期奶陈,因?yàn)樗械男阅軆?yōu)化歷程都勢(shì)必會(huì)經(jīng)歷從高收益到低收益的變化,那么相應(yīng)的在排布優(yōu)化項(xiàng)的前后順序時(shí)也需順應(yīng)此規(guī)律附较,最終呈現(xiàn)的態(tài)勢(shì)即為:前期以小成本快速降低大盤(pán)啟動(dòng)耗時(shí)吃粒,后期逐步提高投入突破各個(gè)瓶頸型耗時(shí)點(diǎn)(更后期大規(guī)模重構(gòu)僅能減少幾十毫秒啟動(dòng)時(shí)間的情形也應(yīng)在預(yù)期之內(nèi)),全過(guò)程同期加強(qiáng)防劣化機(jī)制拒课,最終做到可持續(xù)優(yōu)化徐勃。
在完成前述的全局優(yōu)先級(jí)排布及方案制定后事示,才算真正來(lái)到了實(shí)施優(yōu)化的階段,在這個(gè)階段所要用到的各類優(yōu)化策略及配合方法在前文方法論部分已有詳細(xì)講述僻肖,在實(shí)戰(zhàn)部分首先要補(bǔ)充一下前述幾類優(yōu)化策略按照“性能無(wú)損”肖爵、“業(yè)務(wù)無(wú)損”的區(qū)別劃分,整體如上圖所示臀脏,此外劝堪,我們會(huì)結(jié)合抖音啟動(dòng)優(yōu)化實(shí)戰(zhàn)經(jīng)驗(yàn)列舉各優(yōu)化策略下可實(shí)施的優(yōu)化項(xiàng),以供參考:
- 正面優(yōu)化:刪減非必要的啟動(dòng)邏輯揉稚、開(kāi)屏頁(yè)與首頁(yè) Activity 合并秒啦、獲取進(jìn)程名從 IPC 轉(zhuǎn)反射方式等;
- 按需優(yōu)化:ContentProvider 中過(guò)早初始化邏輯轉(zhuǎn)為使用時(shí)初始化搀玖、多進(jìn)程由啟動(dòng)時(shí)加載轉(zhuǎn)為使用時(shí)或特定場(chǎng)景觸發(fā)加載等余境;
- 延遲優(yōu)化:4.x 機(jī)型延遲執(zhí)行 Multidex.install 中的 Odex 操作、主線程消息隊(duì)列中非啟動(dòng)必要消息延遲執(zhí)行灌诅、啟動(dòng)路徑非高優(yōu)業(yè)務(wù)邏輯延遲初始化等芳来;
- 運(yùn)行時(shí)優(yōu)化:CPU 提頻、語(yǔ)言層面優(yōu)化(內(nèi)聯(lián)延塑、替換反射绣张、避免用 Kotlin 的 Range 循環(huán))答渔、關(guān)閉 Verify Class、4.x 機(jī)型抑制 GC宋雏、主動(dòng)觸發(fā) AOT 編譯磨总、資源重排蚪燕、類重排、dex 重排等汹桦;
- 異步優(yōu)化:異步預(yù)加載(ShardPreference钥弯、實(shí)例化對(duì)象)总处、異步 inflate view辨泳、線程收斂等菠红;
- 降級(jí)優(yōu)化:極速版试溯、組件化降級(jí)遇绞、非必要耗時(shí)邏輯按人群/地區(qū)降級(jí)等摹闽;
- 綜合優(yōu)化:?jiǎn)?dòng)任務(wù)調(diào)度框架、啟動(dòng)路徑重構(gòu)舵匾、前后臺(tái)啟動(dòng)任務(wù)精細(xì)化重排坐梯、后臺(tái)負(fù)載優(yōu)化等,這些優(yōu)化項(xiàng)屬于前述優(yōu)化思想的綜合應(yīng)用蹋辅,一般不局限于單方面的優(yōu)化晕翠。
通過(guò)上述列舉的各策略優(yōu)化項(xiàng)你可能會(huì)發(fā)現(xiàn),這其中有的優(yōu)化項(xiàng)其實(shí)會(huì)對(duì)個(gè)別業(yè)務(wù)性能或功能有損樊卓,但最終對(duì)于啟動(dòng)性能是有顯著提升的浇辜,那么此時(shí)需要按照“全局收益最大”的策略來(lái)綜合評(píng)估這些優(yōu)化項(xiàng)的可落地性柳洋,并不是只看單點(diǎn)的得失,這種全局性的思維在性能優(yōu)化中非常重要募书。
線上驗(yàn)證的實(shí)踐
這部分在前述的方法論中已針對(duì)三個(gè)關(guān)鍵點(diǎn)闡述得比較細(xì)致鬼吵,這里僅針對(duì)三個(gè)關(guān)鍵點(diǎn)在落地時(shí)的技巧或注意事項(xiàng)加以補(bǔ)充:
- 線下的優(yōu)化一定要有線上的指標(biāo)反饋:由于線上設(shè)備的固有硬件性能各異,所以需要有足夠量級(jí)的用戶啟動(dòng)打點(diǎn)數(shù)據(jù)才能相對(duì)準(zhǔn)確地判定線下的優(yōu)化是否在線上產(chǎn)生了效果媒咳,這個(gè)量級(jí)從抖音啟動(dòng)優(yōu)化中摸索的經(jīng)驗(yàn)來(lái)看一般達(dá)到 100 萬(wàn)即可顽耳;此外膝迎,觀測(cè)啟動(dòng)性能數(shù)據(jù)的時(shí)間點(diǎn)也需要把控好限次,這是由于每次發(fā)布升級(jí)版 APP 后,大都是性能相對(duì)好的手機(jī)會(huì)先升級(jí)旱幼,這個(gè)現(xiàn)象會(huì)等導(dǎo)致發(fā)版初期的啟動(dòng)性能數(shù)據(jù)整體偏好柏卤,不能反映真實(shí)大盤(pán)情形,因此忙灼,抖音一般會(huì)選取每個(gè)版本發(fā)版后 4-5 天(可能隨 APP 升級(jí)覆蓋安裝的速度不同而不同)的數(shù)據(jù)判定大盤(pán)情形。
- 線上指標(biāo)需要結(jié)合均值與分位值綜合來(lái)評(píng)估:在抖音啟動(dòng)優(yōu)化實(shí)踐中帅韧,啟動(dòng)耗時(shí)均值會(huì)更多用于大盤(pán)情形評(píng)估或線上監(jiān)控中双妨,而作為性能優(yōu)化的同學(xué)最主要關(guān)注的是 50 分位機(jī)型的數(shù)據(jù)浩姥,這是能代表過(guò)半數(shù)用戶啟動(dòng)性能水準(zhǔn)的指標(biāo)勒叠,此外 90 分位以上的機(jī)型也需要我們額外關(guān)注拌汇,這類機(jī)型非常容易放大啟動(dòng)性能問(wèn)題弊决,從實(shí)際來(lái)看噪舀,90 以上相對(duì) 50 的絕對(duì)分位數(shù)差了不到一倍,但冷啟耗時(shí)卻可能差到 2 倍左右(如下圖所示抖音在某段時(shí)期的各分位冷啟耗時(shí)情形),這說(shuō)明低端機(jī)的用戶啟動(dòng)體驗(yàn)是明顯可感知的差与倡,基于我們?cè)?jīng)做過(guò)的劣化實(shí)驗(yàn)結(jié)果來(lái)看先改,這些機(jī)型的啟動(dòng)性能如果不能有效提升,將有很大概率減少其留存蒸走。
- 在驗(yàn)證收益時(shí)通過(guò) AB 實(shí)驗(yàn)達(dá)成:AB 實(shí)驗(yàn)相對(duì)于觀測(cè)不同版本的大盤(pán)數(shù)據(jù)來(lái)看更具有嚴(yán)謹(jǐn)性比驻,因此在產(chǎn)出實(shí)驗(yàn)結(jié)論前同樣需要保障數(shù)據(jù)量和時(shí)間跨度氯庆,抖音在開(kāi)啟性能的 AB 實(shí)驗(yàn)后,一般會(huì)讓對(duì)照組及實(shí)驗(yàn)組進(jìn)組用戶各達(dá)到 100 萬(wàn)并保持至少 5 天后才進(jìn)行實(shí)驗(yàn)的數(shù)據(jù)分析并產(chǎn)出結(jié)論刁卜,這樣可以基本保障所有相關(guān)指標(biāo)的穩(wěn)定及置信之拨。
防劣化的實(shí)踐
防劣化的體系建設(shè)是個(gè)比較復(fù)雜的工程,要做好是有非常大的挑戰(zhàn)的镀赌。抖音從最早的線下手動(dòng)的分版本測(cè)試開(kāi)始,經(jīng)過(guò)了逐步的摸索優(yōu)化,演變到當(dāng)前涵蓋了代碼提交時(shí)靜態(tài)檢測(cè)啸如、線下自動(dòng)化劣化測(cè)試和歸因帘不、灰度劣化發(fā)現(xiàn)和歸因呛牲、線上常態(tài)化的劣化監(jiān)控和歸因涮阔。防劣化是一個(gè)漏斗减俏,從代碼提交階段到線下測(cè)試階段,再到灰度發(fā)布階段春弥,再到線上版本發(fā)布階段逃呼,我們希望劣化能夠更前置的發(fā)現(xiàn),每個(gè)環(huán)節(jié)都盡可能的發(fā)現(xiàn)解決更多的劣化者娱,保證更少的劣化被帶到線上抡笼。
防劣化的有幾個(gè)難點(diǎn):一是劣化檢測(cè)的準(zhǔn)確率和召回率校翔,為了更多更準(zhǔn)確的發(fā)現(xiàn)劣化衔憨;二是劣化的準(zhǔn)確歸因眉厨,發(fā)現(xiàn)劣化之后,如果不能精準(zhǔn)的指出劣化的原因扣讼,需要投入比較多的人力資源和時(shí)間定位劣化原因缺猛,影響劣化解決的效率;三是劣化的修復(fù)椭符,如果是比較嚴(yán)重的劣化荔燎,可以采用阻塞發(fā)版限期解決的方式,是比較容易推進(jìn)解決的销钝。但是從抖音的實(shí)踐來(lái)看有咨,當(dāng)啟動(dòng)優(yōu)化到了深水區(qū)之后,優(yōu)化的速度已經(jīng)比較緩慢蒸健,需要關(guān)注幾十毫秒級(jí)別的劣化了座享,假設(shè)我們解決了一二兩個(gè)難點(diǎn)婉商,發(fā)現(xiàn)了這些輕微的劣化,但是如何推進(jìn)業(yè)務(wù)去解決這些小劣化也同樣是一個(gè)難題渣叛。我們需要能夠量化出這些劣化對(duì)業(yè)務(wù)的影響丈秩,針對(duì)不同的劣化量級(jí),和業(yè)務(wù)對(duì)齊優(yōu)先級(jí)淳衙,確定標(biāo)準(zhǔn)的劣化修復(fù)流程蘑秽,才能夠保證劣化不會(huì)被帶到線上影響大盤(pán)用戶。
防劣化是一個(gè)長(zhǎng)期的工作箫攀,抖音投入已經(jīng)有一年多了肠牲,目前整體效果還不錯(cuò),在這個(gè)過(guò)程中也積累了比較多的經(jīng)驗(yàn)靴跛,之后會(huì)專門(mén)寫(xiě)一個(gè)抖音的防劣化系列文章來(lái)給大家介紹我們的技術(shù)成果缀雳。
啟動(dòng)優(yōu)化工具
古人云“工欲善其事必先利其器”,在啟動(dòng)性能優(yōu)化領(lǐng)域也是一樣梢睛,我們不僅需要趁手的工具來(lái)定位優(yōu)化耗時(shí)問(wèn)題肥印,還需要盡量自動(dòng)化的工具來(lái)持續(xù)發(fā)現(xiàn)劣化問(wèn)題,也就是說(shuō)整個(gè)啟動(dòng)優(yōu)化在“攻”和“守”的兩大方面均需要工具的輔助扬绪。那么下面將針對(duì)這兩部分的工具分別進(jìn)行介紹及分享抖音在啟動(dòng)優(yōu)化工具方面的探索:
線下分析工具
這部分主要針對(duì)業(yè)界常見(jiàn)的 APP 性能探測(cè)工具進(jìn)行基本原理解析及優(yōu)缺點(diǎn)對(duì)比竖独,具體包含的工具有:TraceView、CPU Profiler挤牛、Systrace莹痢,此外還將提及抖音自研的“新一代全能型性能分析工具 RheaTrace”:
- TraceView:Instrumentation 模式下采用 AddListener 的方式注冊(cè) MethodError、MethodExited墓赴、MethodUnwind 的回調(diào)來(lái)采集方法起止時(shí)間竞膳;Sampling 模式下使用一個(gè) SamplingThread 定時(shí)主權(quán)線程堆棧,通過(guò)對(duì)此的堆棧對(duì)比近似確定函數(shù)的進(jìn)入和退出時(shí)間诫硕;雖然是官方提供的工具坦辟,但兩種模式本身都存在比較大的性能損耗,可能帶偏優(yōu)化方向章办;
- CPU Profiler:整體通過(guò) JVM Agent 實(shí)現(xiàn)锉走,具有完成方法調(diào)用棧輸出,且支持 Java藕届、C/C++方法的耗時(shí)檢測(cè)挪蹭,上手比較簡(jiǎn)單,但其同樣存在性能損耗較大的問(wèn)題休偶,且一般僅用于 debug 包梁厉,release 包需要額外添加 debuggable 的配置;
- Systrace:基于 Android 系統(tǒng)層的 Atrace 實(shí)現(xiàn)踏兜,Atrace 又基于 Linux kernal 層的 ftrace 實(shí)現(xiàn)词顾,ftrace 在內(nèi)核中通過(guò)函數(shù)插樁獲取耗時(shí)八秃;其自身性能損耗比較低、數(shù)據(jù)源豐富且具有較好的可視化頁(yè)面肉盹,但其默認(rèn)監(jiān)控點(diǎn)較少昔驱,在 APP 自有代碼中的監(jiān)控點(diǎn)需要手動(dòng)加入,比較麻煩垮媒;
- RheaTrace:這是抖音基于字節(jié)碼插樁結(jié)合 Systrace 及 Atrace 自研的工具舍悯,其具有自動(dòng)加入監(jiān)控點(diǎn)航棱、各類耗時(shí)信息全面睡雇、性能損耗低等特點(diǎn),是抖音日常在線下實(shí)施性能優(yōu)化時(shí)首選的工具饮醇,其細(xì)節(jié)詳見(jiàn)前述公眾號(hào)文章它抱,這里不再贅述。
RheaTrace 目前是抖音性能優(yōu)化同學(xué)的主要工具朴艰,它不僅僅是一個(gè)工具观蓄,也是一個(gè)平臺(tái)。除了 Systrace 自帶的性能數(shù)據(jù)之外祠墅,我們?cè)黾恿?strong>業(yè)務(wù)的函數(shù)耗時(shí)插樁的數(shù)據(jù)侮穿,可以更全面地對(duì)耗時(shí)進(jìn)行分析。但是這些數(shù)據(jù)還不夠毁嗦,我們支持以插件的形式亲茅,增加自己定制的數(shù)據(jù),比如為了優(yōu)化 IO 的耗時(shí)狗准,我們通過(guò) hook 增加了更精細(xì)化的 IO 的信息克锣,輔助定位 IO 的耗時(shí)問(wèn)題;抖音的類加載耗時(shí)也是有些嚴(yán)重腔长,我們也 hook 了類加載袭祟,增加了類加載的性能數(shù)據(jù)。我們要極致地優(yōu)化抖音啟動(dòng)時(shí)間捞附,以上這些數(shù)據(jù)是不夠的巾乳,還有鎖、View 耗時(shí)信息等相關(guān)數(shù)據(jù)補(bǔ)充鸟召,給性能優(yōu)化的同學(xué)提供全方位的性能分析工具胆绊。
除了 RheaTrace 之外,還有一些特定場(chǎng)景的小工具药版,比如線程分析工具辑舷、內(nèi)存分析工具、高頻函數(shù)分析等槽片。由于篇幅有限何缓,就不在這里一一介紹肢础,后面會(huì)有專門(mén)的系列文章來(lái)介紹。
線上監(jiān)控工具
上面介紹啟動(dòng)優(yōu)化方法論的時(shí)候我們提到了碌廓,不能只是看線下的性能分析传轰,線下的分析結(jié)果并不能完全代表線上大盤(pán)用戶的情況。我們分析線上的性能數(shù)據(jù)谷婆,一方面能夠驗(yàn)證我們的線上優(yōu)化效果慨蛙,另一方面能夠從線上多個(gè)維度的數(shù)據(jù)里指導(dǎo)后續(xù)的優(yōu)化方向。
線上監(jiān)控工具和線下的差異點(diǎn)主要在低性能損耗和兼容性纪挎,我們將 RheaTrace 做了改造期贫,使其能夠滿足線上的監(jiān)控要求。性能損耗上异袄,我們將監(jiān)控的性能損耗控制在 1%以內(nèi)通砍,包大小控制在 200KB 以內(nèi),基本實(shí)現(xiàn)了線上全量用戶的啟動(dòng)耗時(shí)監(jiān)控烤蜕。通過(guò)啟動(dòng)路徑的全量插樁封孙,可以針對(duì)啟動(dòng)路徑的各個(gè)階段進(jìn)行監(jiān)控,一是可以發(fā)現(xiàn)線上用戶哪些任務(wù)比較耗時(shí)讽营,可以針對(duì)性的優(yōu)化虎忌,讓更多用戶受益;二是可以監(jiān)控線上的啟動(dòng)任務(wù)橱鹏,如果發(fā)生了耗時(shí)增加膜蠢,那么說(shuō)明有劣化,這比監(jiān)控到啟動(dòng)時(shí)間的劣化蚀瘸,要更容易定位到原因狡蝶。除了線上的全量慢函數(shù)監(jiān)控之外,我們的線上啟動(dòng)監(jiān)控還會(huì)細(xì)化IO贮勃、鎖贪惹、GC等多種維度的耗時(shí)數(shù)據(jù),幫助定位線上為什么耗時(shí)慢寂嘉,提供新的優(yōu)化方向奏瞬。
總結(jié)一下線上啟動(dòng)監(jiān)控工具的思路就是:將線下的性能分析數(shù)據(jù),低損耗的移植到線上泉孩,觀察線上用戶的性能數(shù)據(jù)硼端,線上線下相結(jié)合的分析啟動(dòng)耗時(shí),為啟動(dòng)優(yōu)化提供優(yōu)化方向指導(dǎo)寓搬。
啟動(dòng)性能優(yōu)化之路去向何方
看了上文關(guān)于啟動(dòng)性能優(yōu)化如此多的理論與實(shí)踐珍昨,想必你已經(jīng)意識(shí)到啟動(dòng)優(yōu)化之路注定是不會(huì)平凡的,抖音在這條路上探索了 2 年之久且仍未到達(dá)盡頭。在這條路上勢(shì)必會(huì)經(jīng)歷前期的坦途镣典、中期的迷茫與后期的瓶頸兔毙,但無(wú)論如何都要一直堅(jiān)定地走下去,因?yàn)橹灰獦I(yè)務(wù)還有一天在迭代那么啟動(dòng)性能就有一天存在挑戰(zhàn)的可能兄春,所以啟動(dòng)優(yōu)化之路的未來(lái)必然是無(wú)盡頭的澎剥。
既然如此,那么我們的重點(diǎn)就應(yīng)該從何時(shí)才能走完這條路轉(zhuǎn)移到如何走得更精彩之上赶舆,甚至到最后能夠做到把控這條路的走向哑姚,這或許也能算作另一種意義上的走完啟動(dòng)優(yōu)化之路,那么什么才算走得更精彩以及把控路的走向呢芜茵?
迷茫時(shí)慢下步子再分析全局的耗時(shí)點(diǎn)尋找到新的優(yōu)化策略叙量、遇到瓶頸時(shí)先暫時(shí)放緩追趕指標(biāo)嘗試從代碼重構(gòu)上挖掘深層的收益、不斷開(kāi)拓跨領(lǐng)域(如端上智能降級(jí))結(jié)合的優(yōu)化方向……這些或許都能稱作是一種精彩夕晓,并且會(huì)因人而異宛乃,最終,當(dāng)這種精彩累計(jì)得足夠多之時(shí)我們很可能會(huì)發(fā)現(xiàn)啟動(dòng)優(yōu)化之路上已知的所有岔路口全被走了個(gè)遍蒸辆,同期 APP 的啟動(dòng)性能也很可能已經(jīng)達(dá)到了再優(yōu)化也沒(méi)什么明顯業(yè)務(wù)收益的地步,并且出現(xiàn)的任何劣化點(diǎn)都能及時(shí)被解決掉析既,那么這時(shí)不出意外的話躬贡,啟動(dòng)優(yōu)化之路走向的把控權(quán)已經(jīng)盡在你手中了。
作者:字節(jié)跳動(dòng)技術(shù)團(tuán)隊(duì)
鏈接:https://juejin.cn/post/7058080006022856735