自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

美團(tuán)外賣iOS App冷啟動(dòng)治理

移動(dòng)開發(fā)
美團(tuán)外賣iOS客戶端從2013年11月開始,歷經(jīng)幾十個(gè)版本的迭代開發(fā),產(chǎn)品形態(tài)不斷完善,業(yè)務(wù)功能日趨復(fù)雜;同時(shí)外賣App也已經(jīng)由原來的獨(dú)立業(yè)務(wù)App演進(jìn)成為一個(gè)平臺(tái)App,陸續(xù)接入了閃購、跑腿等其他新業(yè)務(wù)。因此,更多更復(fù)雜的工作需要在App冷啟動(dòng)的時(shí)候被完成,這給App的冷啟動(dòng)性能帶來了挑戰(zhàn)。

一、背景

冷啟動(dòng)時(shí)長是App性能的重要指標(biāo),作為用戶體驗(yàn)的第一道“門”,直接決定著用戶對(duì)App的第一印象。美團(tuán)外賣iOS客戶端從2013年11月開始,歷經(jīng)幾十個(gè)版本的迭代開發(fā),產(chǎn)品形態(tài)不斷完善,業(yè)務(wù)功能日趨復(fù)雜;同時(shí)外賣App也已經(jīng)由原來的獨(dú)立業(yè)務(wù)App演進(jìn)成為一個(gè)平臺(tái)App,陸續(xù)接入了閃購、跑腿等其他新業(yè)務(wù)。因此,更多更復(fù)雜的工作需要在App冷啟動(dòng)的時(shí)候被完成,這給App的冷啟動(dòng)性能帶來了挑戰(zhàn)。對(duì)此,我們團(tuán)隊(duì)基于業(yè)務(wù)形態(tài)的變化和外賣App的特點(diǎn),對(duì)冷啟動(dòng)進(jìn)行了持續(xù)且有針對(duì)性的優(yōu)化工作,目的就是為了呈現(xiàn)更加流暢的用戶體驗(yàn)。

[[251799]]

二、冷啟動(dòng)定義

一般而言,大家把iOS冷啟動(dòng)的過程定義為:從用戶點(diǎn)擊App圖標(biāo)開始到appDelegate didFinishLaunching方法執(zhí)行完成為止。這個(gè)過程主要分為兩個(gè)階段:

  • T1:main()函數(shù)之前,即操作系統(tǒng)加載App可執(zhí)行文件到內(nèi)存,然后執(zhí)行一系列的加載&鏈接等工作,最后執(zhí)行至App的main()函數(shù)。
  • T2:main()函數(shù)之后,即從main()開始,到appDelegate的didFinishLaunchingWithOptions方法執(zhí)行完畢。

美團(tuán)外賣iOS App冷啟動(dòng)治理

然而,當(dāng)didFinishLaunchingWithOptions執(zhí)行完成時(shí),用戶還沒有看到App的主界面,也不能開始使用App。例如在外賣App中,App還需要做一些初始化工作,然后經(jīng)歷定位、首頁請(qǐng)求、首頁渲染等過程后,用戶才能真正看到數(shù)據(jù)內(nèi)容并開始使用,我們認(rèn)為這個(gè)時(shí)候冷啟動(dòng)才算完成。我們把這個(gè)過程定義為T3。

美團(tuán)外賣iOS App冷啟動(dòng)治理

綜上,外賣App把冷啟動(dòng)過程定義為:從用戶點(diǎn)擊App圖標(biāo)開始到用戶能看到App主界面內(nèi)容為止這個(gè)過程,即T1+T2+T3。在App冷啟動(dòng)過程當(dāng)中,這三個(gè)階段中的每個(gè)階段都存在很多可以被優(yōu)化的點(diǎn)。

三、問題現(xiàn)狀

性能存量問題

美團(tuán)外賣iOS客戶端經(jīng)過幾十個(gè)版本的迭代開發(fā)后,在冷啟動(dòng)過程中已經(jīng)積累了若干性能問題,解決這些性能瓶頸是冷啟動(dòng)優(yōu)化工作的首要目標(biāo),這些問題主要包括:

美團(tuán)外賣iOS App冷啟動(dòng)治理

注:啟動(dòng)項(xiàng)的定義,在App啟動(dòng)過程中需要被完成的某項(xiàng)工作,我們稱之為一個(gè)啟動(dòng)項(xiàng)。例如某個(gè)SDK的初始化、某個(gè)功能的預(yù)加載等。

性能增量問題

一般情況下,在App早期階段,冷啟動(dòng)不會(huì)有明顯的性能問題。冷啟動(dòng)性能問題也不是在某個(gè)版本突然出現(xiàn)的,而是隨著版本迭代,App功能越來越復(fù)雜,啟動(dòng)任務(wù)越來越多,冷啟動(dòng)時(shí)間也一點(diǎn)點(diǎn)延長。最后當(dāng)我們注意到,并想要優(yōu)化它的時(shí)候,這個(gè)問題已經(jīng)變得很棘手了。外賣App的性能問題增量主要來自啟動(dòng)項(xiàng)的增加,隨著版本迭代,啟動(dòng)項(xiàng)任務(wù)簡單粗暴地堆積在啟動(dòng)流程中。如果每個(gè)版本冷啟動(dòng)時(shí)間增加0.1s,那么幾個(gè)版本下來,冷啟動(dòng)時(shí)長就會(huì)明顯增加很多。

四、治理思路

冷啟動(dòng)性能問題的治理目標(biāo)主要有三個(gè):

  • 解決存量問題:優(yōu)化當(dāng)前性能瓶頸點(diǎn),優(yōu)化啟動(dòng)流程,縮短冷啟動(dòng)時(shí)間。
  • 管控增量問題:冷啟動(dòng)流程規(guī)范化,通過代碼范式和文檔指導(dǎo)后續(xù)冷啟動(dòng)過程代碼的維護(hù),控制時(shí)間增量。
  • 完善監(jiān)控:完善冷啟動(dòng)性能指標(biāo)監(jiān)控,收集更詳細(xì)的數(shù)據(jù),及時(shí)發(fā)現(xiàn)性能問題。

美團(tuán)外賣iOS App冷啟動(dòng)治理

五、規(guī)范啟動(dòng)流程

截止至2017年底,美團(tuán)外賣用戶數(shù)已達(dá)2.5億,而美團(tuán)外賣App也已完成了從支撐單一業(yè)務(wù)的App到支持多業(yè)務(wù)的平臺(tái)型App的演進(jìn)(美團(tuán)外賣iOS多端復(fù)用的推動(dòng)、支撐與思考),公司的一些新興業(yè)務(wù)也陸續(xù)集成到外賣App當(dāng)中。下面是外賣App的架構(gòu)圖,外賣的架構(gòu)主要分為三層,底層是基礎(chǔ)組件層,中層是外賣平臺(tái)層,平臺(tái)層向下管理基礎(chǔ)組件,向上為業(yè)務(wù)組件提供統(tǒng)一的適配接口,上層是基礎(chǔ)組件層,包括外賣業(yè)務(wù)拆分的子業(yè)務(wù)組件(外賣App和美團(tuán)App中的外賣頻道可以復(fù)用子業(yè)務(wù)組件)和接入的其他非外賣業(yè)務(wù)。

美團(tuán)外賣iOS App冷啟動(dòng)治理

App的平臺(tái)化為業(yè)務(wù)方提供了高效、標(biāo)準(zhǔn)的統(tǒng)一平臺(tái),但與此同時(shí),平臺(tái)化和業(yè)務(wù)的快速迭代也給冷啟動(dòng)帶來了問題:

  • 現(xiàn)有的啟動(dòng)項(xiàng)堆積嚴(yán)重,拖慢啟動(dòng)速度。
  • 新的啟動(dòng)項(xiàng)缺乏添加范式,雜亂無章,修改風(fēng)險(xiǎn)大,難以閱讀和維護(hù)。

面對(duì)這個(gè)問題,我們首先梳理了目前啟動(dòng)流程中所有的啟動(dòng)項(xiàng),然后針對(duì)App平臺(tái)化設(shè)計(jì)了新的啟動(dòng)項(xiàng)管理方式:分階段啟動(dòng)和啟動(dòng)項(xiàng)自注冊(cè)

美團(tuán)外賣iOS App冷啟動(dòng)治理

分階段啟動(dòng)

早期由于業(yè)務(wù)比較簡單,所有啟動(dòng)項(xiàng)都是不加以區(qū)分,簡單地堆積到didFinishLaunchingWithOptions方法中,但隨著業(yè)務(wù)的增加,越來越多的啟動(dòng)項(xiàng)代碼堆積在一起,性能較差,代碼臃腫而混亂。

美團(tuán)外賣iOS App冷啟動(dòng)治理

通過對(duì)SDK的梳理和分析,我們發(fā)現(xiàn)啟動(dòng)項(xiàng)也需要根據(jù)所完成的任務(wù)被分類,有些啟動(dòng)項(xiàng)是需要?jiǎng)倖?dòng)就執(zhí)行的操作,如Crash監(jiān)控、統(tǒng)計(jì)上報(bào)等,否則會(huì)導(dǎo)致信息收集的缺失;有些啟動(dòng)項(xiàng)需要在較早的時(shí)間節(jié)點(diǎn)完成,例如一些提供用戶信息的SDK、定位功能的初始化、網(wǎng)絡(luò)初始化等;有些啟動(dòng)項(xiàng)則可以被延遲執(zhí)行,如一些自定義配置,一些業(yè)務(wù)服務(wù)的調(diào)用、支付SDK、地圖SDK等。我們所做的分階段啟動(dòng),首先就是把啟動(dòng)流程合理地劃分為若干個(gè)啟動(dòng)階段,然后依據(jù)每個(gè)啟動(dòng)項(xiàng)所做的事情的優(yōu)先級(jí)把它們分配到相應(yīng)的啟動(dòng)階段,優(yōu)先級(jí)高的放在靠前的階段,優(yōu)先級(jí)低的放在靠后的階段。

美團(tuán)外賣iOS App冷啟動(dòng)治理

下面是我們對(duì)美團(tuán)外賣App啟動(dòng)階段進(jìn)行的重新定義,對(duì)所有啟動(dòng)項(xiàng)進(jìn)行的梳理和重新分類,把它們對(duì)應(yīng)到合理的啟動(dòng)階段。這樣做一方面可以推遲執(zhí)行那些不必過早執(zhí)行的啟動(dòng)項(xiàng),縮短啟動(dòng)時(shí)間;另一方面,把啟動(dòng)項(xiàng)進(jìn)行歸類,方便后續(xù)的閱讀和維護(hù)。然后把這些規(guī)則落地為啟動(dòng)項(xiàng)的維護(hù)文檔,指導(dǎo)后續(xù)啟動(dòng)項(xiàng)的新增和維護(hù)。

美團(tuán)外賣iOS App冷啟動(dòng)治理

通過上面的工作,我們梳理出了十幾個(gè)可以推遲執(zhí)行的啟動(dòng)項(xiàng),占所有啟動(dòng)項(xiàng)的30%左右,有效地優(yōu)化了啟動(dòng)項(xiàng)所占的這部分冷啟動(dòng)時(shí)間。

啟動(dòng)項(xiàng)自注冊(cè)

確定了啟動(dòng)項(xiàng)分階段啟動(dòng)的方案后,我們面對(duì)的問題就是如何執(zhí)行這些啟動(dòng)項(xiàng)。比較容易想到的方案是:在啟動(dòng)時(shí)創(chuàng)建一個(gè)啟動(dòng)管理器,然后讀取所有啟動(dòng)項(xiàng),然后當(dāng)時(shí)間節(jié)點(diǎn)到來時(shí)由啟動(dòng)器觸發(fā)啟動(dòng)項(xiàng)執(zhí)行。這種方式存在兩個(gè)問題:

  1. 所有啟動(dòng)項(xiàng)都要預(yù)先寫到一個(gè)文件中(在.m文件import,或用.plist文件組織),這種中心化的寫法會(huì)導(dǎo)致臃腫的代碼,難以閱讀維護(hù)。
  2. 啟動(dòng)項(xiàng)代碼無法復(fù)用:啟動(dòng)項(xiàng)無法收斂到子業(yè)務(wù)庫內(nèi)部,在外賣App和美團(tuán)App中要重復(fù)實(shí)現(xiàn),和外賣App平臺(tái)化的方向不符。

美團(tuán)外賣iOS App冷啟動(dòng)治理

而我們希望的方式是,啟動(dòng)項(xiàng)維護(hù)方式可插拔,啟動(dòng)項(xiàng)之間、業(yè)務(wù)模塊之間不耦合,且一次實(shí)現(xiàn)可在兩端復(fù)用。下圖是我們采用的啟動(dòng)項(xiàng)管理方式,我們稱之為啟動(dòng)項(xiàng)的自注冊(cè):一個(gè)啟動(dòng)項(xiàng)定義在子業(yè)務(wù)模塊內(nèi)部,被封裝成一個(gè)方法,并且自聲明啟動(dòng)階段(例如一個(gè)啟動(dòng)項(xiàng)A,在獨(dú)立App中可以聲明為在willFinishLaunch階段被執(zhí)行,在美團(tuán)App中則聲明在resignActive階段被執(zhí)行)。這種方式下,啟動(dòng)項(xiàng)即實(shí)現(xiàn)了兩端復(fù)用,不相關(guān)的啟動(dòng)項(xiàng)互相隔離,添加/刪除啟動(dòng)項(xiàng)都更加方便。

美團(tuán)外賣iOS App冷啟動(dòng)治理

那么如何給一個(gè)啟動(dòng)項(xiàng)聲明啟動(dòng)階段?又如何在正確的時(shí)機(jī)觸發(fā)啟動(dòng)項(xiàng)的執(zhí)行呢?在代碼上,一個(gè)啟動(dòng)項(xiàng)最終都會(huì)對(duì)應(yīng)到一個(gè)函數(shù)的執(zhí)行,所以在運(yùn)行時(shí)只要能獲取到函數(shù)的指針,就可以觸發(fā)啟動(dòng)項(xiàng)。美團(tuán)平臺(tái)開發(fā)的組件啟動(dòng)治理基建Kylin正是這樣做的:Kylin的核心思想就是在編譯時(shí)把數(shù)據(jù)(如函數(shù)指針)寫入到可執(zhí)行文件的__DATA段中,運(yùn)行時(shí)再從__DATA段取出數(shù)據(jù)進(jìn)行相應(yīng)的操作(調(diào)用函數(shù))。

為什么要用借用__DATA段呢?原因就是為了能夠覆蓋所有的啟動(dòng)階段,例如main()之前的階段。

美團(tuán)外賣iOS App冷啟動(dòng)治理

Kylin實(shí)現(xiàn)原理簡述:Clang 提供了很多的編譯器函數(shù),它們可以完成不同的功能。其中一種就是 section() 函數(shù),section()函數(shù)提供了二進(jìn)制段的讀寫能力,它可以將一些編譯期就可以確定的常量寫入數(shù)據(jù)段。 在具體的實(shí)現(xiàn)中,主要分為編譯期和運(yùn)行時(shí)兩個(gè)部分。在編譯期,編譯器會(huì)將標(biāo)記了 attribute((section())) 的數(shù)據(jù)寫到指定的數(shù)據(jù)段中,例如寫一個(gè){key(key代表不同的啟動(dòng)階段), *pointer}對(duì)到數(shù)據(jù)段。到運(yùn)行時(shí),在合適的時(shí)間節(jié)點(diǎn),在根據(jù)key讀取出函數(shù)指針,完成函數(shù)的調(diào)用。

上述方式,可以封裝成一個(gè)宏,來達(dá)到代碼的簡化,以調(diào)用宏 KLN_STRINGS_EXPORT("Key", "Value")為例,最終會(huì)被展開為:

  1. __attribute__((used, section("__DATA" "," "__kylin__"))) static const KLN_DATA __kylin__0 = (KLN_DATA){(KLN_DATA_HEADER){"Key", KLN_STRING, KLN_IS_ARRAY}, "Value"}; 

使用示例,編譯器把啟動(dòng)項(xiàng)函數(shù)注冊(cè)到啟動(dòng)階段A:

 

  1. KLN_FUNCTIONS_EXPORT(STAGE_KEY_A)() { // 在a.m文件中,通過注冊(cè)宏,把啟動(dòng)項(xiàng)A聲明為在STAGE_KEY_A階段執(zhí)行 
  2.     // 啟動(dòng)項(xiàng)代碼A 
  3.  
  4. KLN_FUNCTIONS_EXPORT(STAGE_KEY_A)() { // 在b.m文件中,把啟動(dòng)項(xiàng)B聲明為在STAGE_KEY_A階段執(zhí)行 
  5.     

在啟動(dòng)流程中,在啟動(dòng)階段STAGE_KEY_A觸發(fā)所有注冊(cè)到STAGE_KEY_A時(shí)間節(jié)點(diǎn)的啟動(dòng)項(xiàng),通過對(duì)這種方式,幾乎沒有任何額外的輔助代碼,我們用一種很簡潔的方式完成了啟動(dòng)項(xiàng)的自注冊(cè)。

 

  1. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 
  2.     // 其他邏輯 
  3.     [[KLNKylin sharedInstance] executeArrayForKey:STAGE_KEY_A];  // 在此觸發(fā)所有注冊(cè)到STAGE_KEY_A時(shí)間節(jié)點(diǎn)的啟動(dòng)項(xiàng) 
  4.     // 其他邏輯 
  5.     return YES; 

完成對(duì)現(xiàn)有的啟動(dòng)項(xiàng)的梳理和優(yōu)化后,我們也輸出了后續(xù)啟動(dòng)項(xiàng)的添加&維護(hù)規(guī)范,規(guī)范后續(xù)啟動(dòng)項(xiàng)的分類原則,優(yōu)先級(jí)和啟動(dòng)階段。目的是管控性能問題增量,保證優(yōu)化成果。

六、優(yōu)化main()之前

在調(diào)用main()函數(shù)之前,基本所有的工作都是由操作系統(tǒng)完成的,開發(fā)者能夠插手的地方不多,所以如果想要優(yōu)化這段時(shí)間,就必須先了解一下,操作系統(tǒng)在main()之前做了什么。main()之前操作系統(tǒng)所做的工作就是把可執(zhí)行文件(Mach-O格式)加載到內(nèi)存空間,然后加載動(dòng)態(tài)鏈接庫dyld,再執(zhí)行一系列動(dòng)態(tài)鏈接操作和初始化操作的過程(加載、綁定、及初始化方法)。這方面的資料網(wǎng)上比較多,但重復(fù)性較高,此處附上一篇WWDC的Topic:Optimizing App Startup Time 。

加載過程—從exec()到main()

真正的加載過程從exec()函數(shù)開始,exec()是一個(gè)系統(tǒng)調(diào)用。操作系統(tǒng)首先為進(jìn)程分配一段內(nèi)存空間,然后執(zhí)行如下操作:

  1. 把App對(duì)應(yīng)的可執(zhí)行文件加載到內(nèi)存。
  2. 把Dyld加載到內(nèi)存。
  3. Dyld進(jìn)行動(dòng)態(tài)鏈接。

美團(tuán)外賣iOS App冷啟動(dòng)治理

下面我們簡要分析一下Dyld在各階段所做的事情:

 

美團(tuán)外賣iOS App冷啟動(dòng)治理

最后 dyld 會(huì)調(diào)用 main() 函數(shù),main() 會(huì)調(diào)用 UIApplicationMain(),before main()的過程也就此完成。

了解完main()之前的加載過程后,我們可以分析出一些影響T1時(shí)間的因素:

  • 動(dòng)態(tài)庫加載越多,啟動(dòng)越慢。
  • ObjC類,方法越多,啟動(dòng)越慢。
  • ObjC的+load越多,啟動(dòng)越慢。
  • C的constructor函數(shù)越多,啟動(dòng)越慢。
  • C++靜態(tài)對(duì)象越多,啟動(dòng)越慢。

針對(duì)以上幾點(diǎn),我們做了如下一些優(yōu)化工作:

代碼瘦身

隨著業(yè)務(wù)的迭代,不斷有新的代碼加入,同時(shí)也會(huì)廢棄掉無用的代碼和資源文件,但是工程中經(jīng)常有無用的代碼和文件被遺棄在角落里,沒有及時(shí)被清理掉。這些無用的部分一方面增大了App的包體積,另一方便也拖慢了App的冷啟動(dòng)速度,所以及時(shí)清理掉這些無用的代碼和資源十分有必要。

通過對(duì)Mach-O文件的了解,可以知道__TEXT:__objcmethname:中包含了代碼中的所有方法,而\_DATA__objc_selrefs中則包含了所有被使用的方法的引用,通過取兩個(gè)集合的差集就可以得到所有未被使用的代碼。核心方法如下,具體可以參考:objc_cover:

 

  1. def referenced_selectors(path): 
  2.     re_sel = re.compile("__TEXT:__objc_methname:(.+)") //獲取所有方法 
  3.     refs = set() 
  4.     lines = os.popen("/usr/bin/otool -v -s __DATA __objc_selrefs %s" % path).readlines() # ios & mac //真正被使用的方法 
  5.     for line in lines: 
  6.         results = re_sel.findall(line) 
  7.         if results: 
  8.             refs.add(results[0]) 
  9.     return refs 

通過這種方法,我們排查了十幾個(gè)無用類和250+無用的方法。

+load優(yōu)化

目前iOS App中或多或少的都會(huì)寫一些+load方法,用于在App啟動(dòng)執(zhí)行一些操作,+load方法在Initializers階段被執(zhí)行,但過多+load方法則會(huì)拖慢啟動(dòng)速度,對(duì)于大中型的App更是如此。通過對(duì)App中+load的方法分析,發(fā)現(xiàn)很多代碼雖然需要在App啟動(dòng)時(shí)較早的時(shí)機(jī)進(jìn)行初始化,但并不需要在+load這樣非??壳暗奈恢?,完全是可以延遲到App冷啟動(dòng)后的某個(gè)時(shí)間節(jié)點(diǎn),例如一些路由操作。其實(shí)+load也可以被當(dāng)做一種啟動(dòng)項(xiàng)來處理,所以在替換+load方法的具體實(shí)現(xiàn)上,我們?nèi)匀徊捎昧松厦娴腒ylin方式。

使用示例:

 

  1. // 用WMAPP_BUSINESS_INIT_AFTER_HOMELOADING聲明替換+load聲明即可,不需其他改動(dòng) 
  2. WMAPP_BUSINESS_INIT_AFTER_HOMELOADING() {  
  3.     // 原+load方法中的代碼 
  4.  
  5. // 在某個(gè)合適的時(shí)機(jī)觸發(fā)注冊(cè)到該階段的所有方法,如冷啟動(dòng)結(jié)束后 
  6. [[KLNKylin sharedInstance] executeArrayForKey:@kWMAPP_BUSINESS_INITIALIZATION_AFTER_HOMELOADING_KEY]  

七、優(yōu)化耗時(shí)操作

在main()之后主要工作是各種啟動(dòng)項(xiàng)的執(zhí)行(上面已經(jīng)敘述),主界面的構(gòu)建,例如TabBarVC,HomeVC等等。資源的加載,如圖片I/O、圖片解碼、archive文檔等。這些操作中可能會(huì)隱含著一些耗時(shí)操作,靠單純閱讀非常難以發(fā)現(xiàn),如何發(fā)現(xiàn)這些耗時(shí)點(diǎn)呢?找到合適的工具就會(huì)事半功倍。

Time Profiler

Time Profiler是Xcode自帶的時(shí)間性能分析工具,它按照固定的時(shí)間間隔來跟蹤每一個(gè)線程的堆棧信息,通過統(tǒng)計(jì)比較時(shí)間間隔之間的堆棧狀態(tài),來推算某個(gè)方法執(zhí)行了多久,并獲得一個(gè)近似值。Time Profiler的使用方法網(wǎng)上有很多使用教程,這里我們也不過多介紹,附上一篇使用文檔:Instruments Tutorial with Swift: Getting Started。

火焰圖

除了Time Profiler,火焰圖也是一個(gè)分析CPU耗時(shí)的利器,相比于Time Profiler,火焰圖更加清晰?;鹧鎴D分析的產(chǎn)物是一張調(diào)用棧耗時(shí)圖片,之所以稱為火焰圖,是因?yàn)檎麄€(gè)圖形看起來就像一團(tuán)跳動(dòng)的火焰,火焰尖部是調(diào)用棧的棧頂,底部是棧底,縱向表示調(diào)用棧的深度,橫向表示消耗的時(shí)間。一個(gè)格子的寬度越大,越說明其可能是瓶頸。分析火焰圖主要就是看那些比較寬大的火苗,特別留意那些類似“平頂山”的火苗。下面是美團(tuán)平臺(tái)開發(fā)的性能分析工具-Caesium的分析效果圖:

美團(tuán)外賣iOS App冷啟動(dòng)治理

通過對(duì)火焰圖的分析,我們發(fā)現(xiàn)了冷啟動(dòng)過程中存在著不少問題,并成功優(yōu)化了0.3S+的時(shí)間。優(yōu)化內(nèi)容總結(jié)如下:

美團(tuán)外賣iOS App冷啟動(dòng)治理

八、優(yōu)化串行操作

在冷啟動(dòng)過程中,有很多操作是串行執(zhí)行的,若干個(gè)任務(wù)串行執(zhí)行,時(shí)間必然比較長。如果能變串行為并行,那么冷啟動(dòng)時(shí)間就能夠大大縮短。

閃屏頁的使用

現(xiàn)在許多App在啟動(dòng)時(shí)并不直接進(jìn)入首頁,而是會(huì)向用戶展示一個(gè)持續(xù)一小段時(shí)間的閃屏頁,如果使用恰當(dāng),這個(gè)閃屏頁就能幫我們節(jié)省一些啟動(dòng)時(shí)間。因?yàn)楫?dāng)一個(gè)App比較復(fù)雜的時(shí)候,啟動(dòng)時(shí)首次構(gòu)建App的UI就是一個(gè)比較耗時(shí)的過程,假定這個(gè)時(shí)間是0.2秒,如果我們是先構(gòu)建首頁UI,然后再在Window上加上這個(gè)閃屏頁,那么冷啟動(dòng)時(shí),App就會(huì)實(shí)實(shí)在在地卡住0.2秒,但是如果我們是先把閃屏頁作為App的RootViewController,那么這個(gè)構(gòu)建過程就會(huì)很快。因?yàn)殚W屏頁只有一個(gè)簡單的ImageView,而這個(gè)ImageView則會(huì)向用戶展示一小段時(shí)間,這時(shí)我們就可以利用這一段時(shí)間來構(gòu)建首頁UI了,一舉兩得。

美團(tuán)外賣iOS App冷啟動(dòng)治理

緩存定位&首頁預(yù)請(qǐng)求

美團(tuán)外賣App冷啟動(dòng)過程中一個(gè)重要的串行流程就是:首頁定位-->首頁請(qǐng)求-->首頁渲染過程,這三個(gè)操作占了整個(gè)首頁加載時(shí)間的77%左右,所以想要縮短冷啟動(dòng)時(shí)間,就一定要從這三點(diǎn)出發(fā)進(jìn)行優(yōu)化。

之前串行操作流程如下:

美團(tuán)外賣iOS App冷啟動(dòng)治理

優(yōu)化后的設(shè)計(jì),在發(fā)起定位的同時(shí),使用客戶端緩存定位,進(jìn)行首頁數(shù)據(jù)的預(yù)請(qǐng)求,使定位和請(qǐng)求并行進(jìn)行。然后當(dāng)用戶真實(shí)定位成功后,判斷真實(shí)定位是否命中緩存定位,如果命中,則剛才的預(yù)請(qǐng)求數(shù)據(jù)有效,這樣可以節(jié)省大概40%的時(shí)間首頁加載時(shí)間,效果非常明顯;如果未命中,則棄用預(yù)請(qǐng)求數(shù)據(jù),重新請(qǐng)求。

美團(tuán)外賣iOS App冷啟動(dòng)治理

九、數(shù)據(jù)監(jiān)控

Time Profiler和Caesium火焰圖都只能在線下分析App在單臺(tái)設(shè)備中的耗時(shí)操作,局限性比較大,無法在線上監(jiān)控App在用戶設(shè)備上的表現(xiàn)。外賣App使用公司內(nèi)部自研的Metrics性能監(jiān)控系統(tǒng),長期監(jiān)控App的性能指標(biāo),幫助我們掌握App在線上各種環(huán)境下的真實(shí)表現(xiàn),并為技術(shù)優(yōu)化項(xiàng)目提供可靠的數(shù)據(jù)支持。Metrics監(jiān)控的核心指標(biāo)之一,就是冷啟動(dòng)時(shí)間。

冷啟動(dòng)開始&結(jié)束時(shí)間節(jié)點(diǎn)

結(jié)束時(shí)間點(diǎn):結(jié)束時(shí)間比較好確定,我們可以將首頁某些視圖元素的展示作為首頁加載完成的標(biāo)志。

開始時(shí)間點(diǎn):一般情況下,我們都是在main()之后才開始接管App,但以main()函數(shù)作為冷啟動(dòng)起始點(diǎn)顯然不合適,因?yàn)檫@樣無法統(tǒng)計(jì)到T1時(shí)間段。那么,起始時(shí)間如何確定呢?目前業(yè)界常見的有兩種方法,一是以可執(zhí)行文件中任意一個(gè)類的+load方法的執(zhí)行時(shí)間作為起始點(diǎn);二是分析dylib的依賴關(guān)系,找到葉子節(jié)點(diǎn)的dylib,然后以其中某個(gè)類的+load方法的執(zhí)行時(shí)間作為起始點(diǎn)。根據(jù)Dyld對(duì)dylib的加載順序,后者的時(shí)機(jī)更早。但是這兩種方法獲取的起始點(diǎn)都只在Initializers階段,而Initializers之前的時(shí)長都沒有被計(jì)入。Metrics則另辟蹊徑,以App的進(jìn)程創(chuàng)建時(shí)間(即exec函數(shù)執(zhí)行時(shí)間)作為冷啟動(dòng)的起始時(shí)間。因?yàn)橄到y(tǒng)允許我們通過sysctl函數(shù)獲得進(jìn)程的有關(guān)信息,其中就包括進(jìn)程創(chuàng)建的時(shí)間戳。

 

  1. #import <sys/sysctl.h> 
  2. #import <mach/mach.h> 
  3.  
  4. + (BOOL)processInfoForPID:(int)pid procInfo:(struct kinfo_proc*)procInfo 
  5.     int cmd[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; 
  6.     size_t size = sizeof(*procInfo); 
  7.     return sysctl(cmd, sizeof(cmd)/sizeof(*cmd), procInfo, &sizeNULL, 0) == 0; 
  8.  
  9. + (NSTimeInterval)processStartTime 
  10.     struct kinfo_proc kProcInfo; 
  11.     if ([self processInfoForPID:[[NSProcessInfo processInfo] processIdentifier] procInfo:&kProcInfo]) { 
  12.         return kProcInfo.kp_proc.p_un.__p_starttime.tv_sec * 1000.0 + kProcInfo.kp_proc.p_un.__p_starttime.tv_usec / 1000.0; 
  13.     } else { 
  14.         NSAssert(NO, @"無法取得進(jìn)程的信息"); 
  15.         return 0; 
  16.     } 

進(jìn)程創(chuàng)建的時(shí)機(jī)非常早。經(jīng)過實(shí)驗(yàn),在一個(gè)新建的空白App中,進(jìn)程創(chuàng)建時(shí)間比葉子節(jié)點(diǎn)dylib中的+load方法執(zhí)行時(shí)間早12ms,比main函數(shù)的執(zhí)行時(shí)間早13ms(實(shí)驗(yàn)設(shè)備:iPhone 7 Plus (iOS 12.0)、Xcode 10.0、Release 模式)。外賣App線上的數(shù)據(jù)則更加明顯,同樣的機(jī)型(iPhone 7 Plus)和系統(tǒng)版本(iOS 12.0),進(jìn)程創(chuàng)建時(shí)間比葉子節(jié)點(diǎn)dylib中的+load方法執(zhí)行時(shí)間早688ms。而在全部機(jī)型和系統(tǒng)版本中,這一數(shù)據(jù)則是878ms。

冷啟動(dòng)過程時(shí)間節(jié)點(diǎn)

我們也在App冷啟動(dòng)過程中的所有關(guān)鍵節(jié)點(diǎn)打上一連串測速點(diǎn),Metrics會(huì)記錄下測速點(diǎn)的名稱,及其距離進(jìn)程創(chuàng)建時(shí)間的時(shí)長。我們沒有采用自動(dòng)打點(diǎn)的方式,是因?yàn)橥赓uApp的冷啟動(dòng)過程十分復(fù)雜,而自動(dòng)打點(diǎn)無法做到如此細(xì)致,并不實(shí)用。另外,Metrics記錄的是時(shí)間軸上以進(jìn)程創(chuàng)建時(shí)間為原點(diǎn)的一組順序的時(shí)間點(diǎn),而不是一組時(shí)間段,是因?yàn)轫樞虻臅r(shí)間點(diǎn)可以計(jì)算任意兩個(gè)時(shí)間點(diǎn)之間的距離,即可以將時(shí)間點(diǎn)處理成時(shí)間段。但是,一組時(shí)間段可能無法還原為順序的時(shí)間點(diǎn),因?yàn)闀r(shí)間段之間可能并不是首尾相接的,特別是對(duì)于異步執(zhí)行或者多線程的情況。

在測速完畢后,Metrics會(huì)統(tǒng)一將所有測速點(diǎn)上報(bào)到后臺(tái)。下圖是美團(tuán)外賣App 6.10版本的部分過程節(jié)點(diǎn)監(jiān)控?cái)?shù)據(jù)截圖:

美團(tuán)外賣iOS App冷啟動(dòng)治理

Metrics還會(huì)由后臺(tái)對(duì)數(shù)據(jù)做聚合計(jì)算,得到冷啟動(dòng)總時(shí)長和各個(gè)測速點(diǎn)時(shí)長的50分位數(shù)、90分位數(shù)和95分位數(shù)的統(tǒng)計(jì)數(shù)據(jù),這樣我們就能從宏觀上對(duì)冷啟動(dòng)時(shí)長分布情況有所了解。下圖中橫軸為時(shí)長,縱軸為上報(bào)的樣本數(shù)。

美團(tuán)外賣iOS App冷啟動(dòng)治理

十、總結(jié)

對(duì)于快速迭代的App,隨著業(yè)務(wù)復(fù)雜度的增加,冷啟動(dòng)時(shí)長會(huì)不可避免的增加。冷啟動(dòng)流程也是一個(gè)比較復(fù)雜的過程,當(dāng)遇到冷啟動(dòng)性能瓶頸時(shí),我們可以根據(jù)App自身的特點(diǎn),配合工具的使用,從多方面、多角度進(jìn)行優(yōu)化。同時(shí),優(yōu)化冷啟動(dòng)存量問題只是冷啟動(dòng)治理的第一步,因?yàn)槔鋯?dòng)性能問題并不是一日造成的,也不能簡單的通過一次優(yōu)化工作就能解決,我們需要通過合理的設(shè)計(jì)、規(guī)范的約束,來有效地管控性能問題的增量,并通過持續(xù)的線上監(jiān)控來及時(shí)發(fā)現(xiàn)并修正性能問題,這樣才能夠長期保證良好的App冷啟動(dòng)體驗(yàn)。

作者簡介

郭賽,美團(tuán)點(diǎn)評(píng)資深工程師。2015年加入美團(tuán),目前作為外賣iOS團(tuán)隊(duì)主力開發(fā),負(fù)責(zé)移動(dòng)端業(yè)務(wù)開發(fā),業(yè)務(wù)類基礎(chǔ)設(shè)施的建設(shè)與維護(hù)。

徐宏,美團(tuán)點(diǎn)評(píng)資深工程師。2016年加入美團(tuán),目前作為外賣iOS團(tuán)隊(duì)主力開發(fā),負(fù)責(zé)移動(dòng)端APM性能監(jiān)控,高可用基礎(chǔ)設(shè)施支撐相關(guān)推進(jìn)工作。

責(zé)任編輯:未麗燕 來源: 美團(tuán)技術(shù)團(tuán)隊(duì)
相關(guān)推薦

2016-11-27 20:43:26

云計(jì)算迭代

2017-06-01 10:52:35

互聯(lián)網(wǎng)

2018-03-28 09:53:50

Android架構(gòu)演進(jìn)

2018-04-23 09:50:54

2022-04-29 09:10:00

算法人工智能技術(shù)

2017-12-08 18:45:41

程序員外賣運(yùn)維

2015-11-16 16:00:21

2020-10-22 15:35:35

自動(dòng)駕駛美團(tuán)人工智能

2022-03-25 10:47:59

架構(gòu)實(shí)踐美團(tuán)

2016-01-14 16:06:19

CIO時(shí)代網(wǎng)

2022-01-05 22:31:26

數(shù)字人民幣加密貨幣區(qū)塊鏈

2017-04-05 16:08:54

外賣商業(yè)變現(xiàn)

2023-08-30 18:49:05

2021-07-15 17:31:44

無人機(jī)人工智能AI

2021-11-23 10:25:35

性能優(yōu)化iOS App 啟動(dòng)優(yōu)化

2019-11-04 14:15:33

微信iOS 13.2APP

2017-03-22 17:51:43

互聯(lián)網(wǎng)

2018-08-03 09:42:01

人工智能深度學(xué)習(xí)人臉識(shí)別

2018-01-19 14:04:05

人工智能機(jī)器學(xué)習(xí)智能語音
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)