阿里資料iOS端啟動速度優化的一些經驗

2021-08-17 17:32:13 字數 3908 閱讀 6722

背景應用啟動流程

ios應用的啟動可分為pre-main階段和main()階段,其中系統做的事情依次是:

pre-main階段

1.1. 載入應用的可執行檔案

1.2. 載入動態鏈結庫載入器dyld(dynamic loader)

1.3. dyld遞迴載入應用所有依賴的dylib(dynamic library 動態鏈結庫)

main()階段

2.1. dyld呼叫main()

2.4. 呼叫didfinishlaunchingwithoptions

啟動耗時的測量

在進行優化之前,我們首先應該能測量各階段的耗時。

pre-main階段

pre-main階段啟動耗時測量.png

pre-main階段啟動耗時測量.png

main()階段

cfabsolutetime starttime;

int main(int argc, char * argv) {

starttime = cfabsolutetimegetcurrent();
extern cfabsolutetime starttime;

最後在didfinishlaunchingwithoptions裡,再獲取一下當前時間,與starttime的差值即是main()階段執行耗時。

double launchtime = (cfabsolutetimegetcurrent() - starttime);

pre-main階段的優化

load dylibs

這一階段dyld會分析應用依賴的dylib,找到其mach-o檔案,開啟和讀取這些檔案並驗證其有效性,接著會找到**簽名註冊到核心,最後對dylib的每乙個segment呼叫mmap()。

一般情況下,ios應用會載入100-400個dylibs,其中大部分是系統庫,這部分dylib的載入系統已經做了優化。

所以,依賴的dylib越少越好。在這一步,我們可以做的優化有:

盡量不使用內嵌(embedded)的dylib,載入內嵌dylib效能開銷較大

合併已有的dylib和使用靜態庫(static archives),減少dylib的使用個數

懶載入dylib,但是要注意dlopen()可能造成一些問題,且實際上懶載入做的工作會更多

rebase/bind

在dylib的載入過程中,系統為了安全考慮,引入了aslr(address space layout randomization)技術和**簽名。由於aslr的存在,映象(image,包括可執行檔案、dylib和bundle)會在隨機的位址上載入,和之前指標指向的位址(preferred_address)會有乙個偏差(slide),dyld需要修正這個偏差,來指向正確的位址。

rebase在前,bind在後,rebase做的是將映象讀入記憶體,修正映象內部的指標,效能消耗主要在io。bind做的是查詢符號表,設定指向映象外部的指標,效能消耗主要在cpu計算。

所以,指標數量越少越好。在這一步,我們可以做的優化有:

減少objc類(class)、方法(selector)、分類(category)的數量

減少c++虛函式的的數量(建立虛函式表有開銷)

使用swift structs(內部做了優化,符號數量更少)

objc setup

大部分objc初始化工作已經在rebase/bind階段做完了,這一步dyld會註冊所有宣告過的objc類,將分類插入到類的方法列表裡,再檢查每個selector的唯一性。

在這一步倒沒什麼優化可做的,rebase/bind階段優化好了,這一步的耗時也會減少。

initializers

到了這一階段,dyld開始執行程式的初始化函式,呼叫每個objc類和分類的+load方法,呼叫c/c++ 中的構造器函式(用attribute((constructor))修飾的函式),和建立非基本型別的c++靜態全域性變數。initializers階段執行完後,dyld開始呼叫main()函式。

在這一步,我們可以做的優化有:

少在類的+load方法裡做事情,盡量把這些事情推遲到+initiailize

減少構造器函式個數,在構造器函式裡少做些事情

減少c++靜態全域性變數的個數

main()階段的優化

這一階段的優化主要是減少didfinishlaunchingwithoptions方法裡的工作,在didfinishlaunchingwithoptions方法裡,我們會建立應用的window,指定其rootviewcontroller,呼叫window的makekeyandvisible方法讓其可見。由於業務需要,我們會初始化各個二方/三方庫,設定系統ui風格,檢查是否需要顯示引導頁、是否需要登入、是否有新版本等,由於歷史原因,這裡的**容易變得比較龐大,啟動耗時難以控制。

所以,滿足業務需要的前提下,didfinishlaunchingwithoptions在主線程裡做的事情越少越好。在這一步,我們可以做的優化有:

梳理業務邏輯,把可以延遲執行的邏輯,做延遲執行處理。比如檢查新版本、註冊推送通知等邏輯。

避免複雜/多餘的計算。

採用效能更好的api。

首頁控制器用純**方式來構建。

阿里資料ios端優化實踐

在以上的認知指導下,阿里資料ios端開始著手優化,在pre-main階段和main()階段分別做了一系列優化,取得了一定的成果。

pre-main階段的優化

1.1. 排查無用的dylib,移除不再使用的libicucore.tbd

1.2. 刪除無用檔案&庫,合併重複檔案(多個重複的分類)。移除不再使用的庫umsocial、pstcollectionview、mcswipetableviewcell,移除功能重複的庫mantle。

1.3. 梳理各個類的+load方法,將多個類中+load方法做的事延遲到+initiailize裡去做。

優化前pre-main階段耗時:

優化前pre-main階段耗時.png

優化後pre-main階段耗時:

優化後pre-main階段耗時.png

測試環境:xcode8.3.3 ios10.2的模擬器,熱啟動。

備註:測試發現,pre-main階段耗時有一定波動,冷啟動時波動更大,這裡截圖貼的是乙個中位數水平。

可以看到熱啟動下,pre-main階段耗時有一定下降。

main()階段的優化

2.1. 去掉其中100ms的dispatch_after…檢查**發現之前會故意讓啟**多顯示100ms,不知道是什麼邏輯…

2.2. 將多個二方/三方庫延遲載入。包括tbcrashreporter、tbaccssdk、ut、tremotedebugger、atsdk等。

2.3. 將若干系統ui配置、業務邏輯延遲執行。包括註冊推送、檢查新版本、更新orange配置等。

2.4. 避免多餘的計算。之前會前後兩次獲取是否要顯示廣告圖,每次獲取都需要反序列化orange中的配置資訊,再比較配置中的開始/結束時間,大約耗時20ms。目前的解決方案是第一次計算後,用乙個bool屬性快取起來,下次直接取用。

通過instruments的time profiler分析,優化後啟動速度有明顯提公升,didfinishlaunchingwithoptions耗時在75ms左右(iphone6s ios10.3.3)

啟動耗時..png

總結&後續規劃總結

總結起來,好像啟動速度優化就一句話:讓系統在啟動期間少做一些事。當然我們得先清楚工程裡做的哪些事是在啟動期間做的、對啟動速度的影響有多大,然後case by case地分析工程**,通過放到子執行緒、延遲載入、懶載入等方式讓系統在啟動期間更輕鬆些。

後續規劃

2.1. 替代部分龐大的庫,採用更輕量級的解決方案。

2.2. 整理**,去除重複的實現,避免出現功能重複的類&分類&方法。

2.3. 梳理和移除已經下線的業務涉及的類&分類&方法。

2.4. 監控好灰度版本啟動速度的變化趨勢,盡早發現&解決拖慢啟動速度的問題。

初學matlab程式設計運算速度的一些優化

學習了matlab了之後發現程式設計的技巧很重要,掌握了正確的程式設計技巧之後計算速度會加快非常多,尤其是面對一些規模龐大的問題,可以減輕計算機的負擔。我了解到優化matlab 的一種方法是對於迴圈內的大陣列進行預先定義 分配空間。用兩個例子可以對比分配記憶體的區別 tic x 1 x 2 2 x ...

labview串列埠讀不到資料問題的一些經驗總結

用labview寫的串列埠一直讀不到資料,找了很久終於發現原因了。在寫串列埠之前,先確定串列埠的配置有沒有錯。如波特率 校驗位 停止位 流控制等。然後是確定硬體能用。包括usb轉串列埠,微控制器。我用的是usb轉ttl,最方便的一種驗證方法就是,把usb轉ttl的rx腳和tx腳短接起來,執行labv...

iOS客戶端socket長連線的一些解決方案

工作以來一直在做ios即時通訊這方面的東西,說到即時通訊,最常見的就是socket方面的知識,socket被問到的最多就是如何保持長連線。這裡就簡單的說些socket的長連線問題。其實簡單,就是利用了ios的推送機制 apns,當檢測到程式與伺服器斷開連線後,當有訊息需要推送時,走的就是蘋果的apn...