波報 | Pofat 的 Swift 中文電子報

Share this post

生存指南 #0 從 App 啟動開始

pofat.substack.com
清道夫生存指南

生存指南 #0 從 App 啟動開始

2023/Feb/22

Pofat
Feb 22
13
Share this post

生存指南 #0 從 App 啟動開始

pofat.substack.com

💬 Pofat 的話

第一話就從一個常被談起的話題開始,絕大多數性能團隊(一個人也算團隊)的第一項工作都是啟動時間的最佳化,而性能界的圭臬是 - 改善前先量測。啟動時間的量測也因此不是新鮮話題了,只是隨著時間演進、iOS 系統的變化,測量方式也在逐漸改變,就讓我們一起看看 2023 年的現在如何測量啟動時間。

《回專欄 | 所有文章》


我要量什麼

首先我們要決定量什麼以及怎麼量,第一個問題很簡單:對 app 啟動來說首要的自然是「時間」;第二個問題就比較複雜,我們要先定義起點和終點,才能決定要怎麼進行下去。

你可能會認為起點是「使用者按下 App Icon」的瞬間,我們也先暫時這樣定義,那終點會在哪?回答這個問題前需要跟大家分享一個重要的心法:

效能指標要有兩種,以「面向使用者」指標為首要,「面向開發者」則為次要。

「面向使用者」的指標要能反應出使用者的感受,畢竟這是絕大多數公司網路公司賴以維生的方向,所以起終點的定義會因產品類型有所不同,一個最常見的定義是 Time To Interactive (下稱 TTI),即從點下 icon 到真正可以開始操作的這段區間,這告訴我們使用者等了多久才可以用 app ;而「面向開發者」的指標則是幫助我們找出問題的其它量測,比如 page fault,DB query 次數,cpu usage 或更多子區間等,因為經歷時間雖對使用者是最有感的指標,但通常卻是對找問題最沒用的。

“Elapsed time is data I can only cry about.” - Rico Mariani

於是我們有了第一個問題的答案:量 TTI,因篇幅關係我們先聚焦在冷啟動(cold start),指的是 app 並不是從背景回到前景的那種啟動,而是從無到有建立一個新的 app 程序(process)。

iOS 啟動的步驟

iOS app 的啟動簡單來說粗分成六個階段(圖片以 iOS 15 之前的 DYLD3 為例,來自 WWDC 19 的 Optimizing App Launch)。

當按下 icon 後,iOS 會建立程序,接著便是:

  1. 載入和鏈結 frameworks (System Interface)

  2. 語言的 runtime 準備,比如 ObjC 的 rebase/binding ,Swift 在 iOS 16 之前會在這裡檢查 Protocol Conformance,意外地造成不小的性能開銷 (Runtime Init)

  3. 進入 main 並開始建立 UIApplication 和 UIApplicationDelegate (UIKit Init)

  4. 開始大家熟悉的 app lifecycle application:willFinishLaunchingWithOptions: 與application:didFinishLaunchingWithOptions: 等(Application Init)

  5. 第一個畫面被 render,同時也代表 Runloop 建立完成、開始工作。注意這裡的第一個畫面並不是我們可以指定的靜態 launch screen,而是程式碼或 storyboard 所畫出的畫面,這個地方也是 MetricKit 和 Firebase 裡 launch metric 的量測終點,也是 TTI 終點的候選點之一(Initial Frame Render)

  6. 接下來就是 app剩下的邏輯,有些需同步的 app 可能會先顯示讀取條或轉圈圈,等到更新後才顯示內容,這也是可以做為 TTI 終點的地方,依所需決定(Extended)

iOS 為你做了什麼

iOS 也一直從系統的角度幫助加速這個流程,於是便有了金句:

「幹一年不如等一年。」 《戴老師》

Twitter avatar for @weak_self
weak self podcast @weak_self
🎙️99: 理直氣壯的綜藝技術節目.幹一年不如等一年 weak self 是個上架一百集的 iOS 工程師 Podcast 節目。 weakself.dev/episodes/99
weakself.dev99: 理直氣壯的綜藝技術節目.幹一年不如等一年weak self podcast
10:14 PM ∙ Jun 19, 2022
13Likes4Retweets

這些年來,iOS 幫忙緩存了 rebase 的結果,也在 iOS 13.4 引入了執行檔頭的新格式(chained fixup)以加速和減小二進位檔案大小,在 iOS 16 時加速了 Swift protocol 遵循的檢查,不過今天的主角是 iOS 15 開始的 prewarm。

Prewarm 會在閒置時由 iOS 在背景喚醒 app ,可是只會執行到main,在閒置時可以先做點前置工作,等到使用者真正要打開 app 時就可以從緩存中取得大多資訊,這功能給量測的實際工作帶來不少困擾,我們先來看看怎麼量測。

好,來量吧

開始前我們先假設已經有了上傳數據的第三方服或自有 API,我們就聚焦在怎麼量測 iOS 端的行為。

要量測經歷的時間,我們需要一個時間軸的零點,而「使用者點下 app icon」的瞬間就是這個零點,看到這結論和上面啟動流程的介結,你可能會思考著,這瞬間發生時都還沒跑到我寫的程式碼,要怎麼取得這個瞬間呢?

當接著 Xcode 開發時,可以下環境變數 DYLD_PRINT_STATISTICS 以在 console 看到相關數據,但 product 環境則無法這麼做。所幸我們可以從 Darwin api 取得程序建立時間(點下 icon的瞬間),便可以回推建立程序到 main 經過的時間,這段期間常稱之為 pre-main。

main 是一個很重要的時間點,這裡是整個紀錄過程的真正基準點,向前能回算零點,向後也要參考這裡取得間隔時間。

不過這個方法在 iOS 15 後會出問題,因為上述的 prewarm,使得程序可能早在使用者點下 icon 前就建立了,回推下去可能會出現一小時這種不合常理的數字,所以 prewarm 的情況必須特別處理, 比如用 main 之後的時間點做為零點,這裡建議標註清楚此類的啟動是 prewarm 以便後續數據處理。Apple 一直沒有官方的說明怎麼偵測 prewarm,不過我們可以從環境變數裡的 ActivePrewarm 來判斷:

到此起點就搞定了,接下來終點一定是在開發者完全可掌控的環境中,所以只要確定在哪基本上就完成了。

註:上面的程式碼可以在 gist 中找到。

測量之外

除了打點上傳數據外,團隊最好有資料工程師、科學家或至少要有具統計觀念的成員來做後續的數據處理、分析,因為收錯數據或解讀錯誤都會讓前置工作失去意義。最後小結一下流程:

  1. 確認對使用者有意義的啟動路徑,定義起終點,以及中間有重大意義的點

  2. 確定關鍵指標 ,比如上述的 TTI,要把它放在 dashboard 的 C 位

  3. 寫一隻 Tracker 收集這些點,間隔時間,以及標記,比如 happy path 標 success,沒網路標 offline,使用者退出到背景等情況

  4. 收獲,分析,改善(量測本身或 app 程式碼)

這期探討了啟動量測的基本方向和定義方式,在這之外還有很多可以聊的,我們日後再繼續深究。

Share this post

生存指南 #0 從 App 啟動開始

pofat.substack.com
Comments
TopNewCommunity

No posts

Ready for more?

© 2023 Pofat
Privacy ∙ Terms ∙ Collection notice
Start WritingGet the app
Substack is the home for great writing