iOS啟動優化(上)-概念篇
前言
啟動App
是給使用者第一印象,如果啟動比較慢,很可能會導致這個使用者流失,那麼啟動時間的優化就顯得尤為重要,本文結合啟動時間從一些基本的概念入手分析
啟動時間
啟動根據main
分為兩個階段:main
函式之前(pre-main
)和main
函式之後,在pre-main
階段,程式最早能執行的程式碼是+load
函式,但load
之前還有很多步驟,例如載入動態庫的時間等,這些我們怎麼去檢測呢,這個時候就需要用到DYLD
檢測,也就是通過配置環境變數,它會將這個階段相關的耗時反饋給開發者。
- 新建個工程,然後cmd + shift + ,
-> Run
-> Arguments
配置環境變數DYLD_PRINT_STATISTICS
,也可以設定DYLD_PRINT_STATISTICS_DETAILS
這個會詳細些。本文配置DYLD_PRINT_STATISTICS
來講
- 在真機上執行,結果如下:
- 這個代表本次pre-main
時間用了338.68
毫秒:
- dylib loading time
:是動態庫載入耗時。系統提供的動態庫已經載入在共享快取空間,已經做了優化,但自定義動態庫並沒有這個優化,蘋果建議建立的動態庫不能超過6
個,如果大於這個數就需要做動態庫合併
- rebase/binding time
:是重定位/繫結耗時,涉及到動態庫合併
- ObjC setup time
:OC註冊類。在做專案時,可以刪除廢棄的(不使用的)類,只要在裡面就會影響啟動時間
- initializer time
:執行load
、建構函式
的耗時。
- slowest intializers
:最慢的動態庫耗時
下面著重講rebase/binding
相關的知識
實體記憶體和虛擬記憶體
實體記憶體
- 在早期的作業系統,
CPU
直接從記憶體條讀取資料,這就導致了記憶體不夠用
問題,如下圖:
- 應用需要載入時就會直接載入到記憶體條中,然後CPU
去記憶體條讀取。當載入的應用多了,再載入新的應用時記憶體就不夠用
了,這時候就需要幹掉一些應用然後再去開啟這個新的應用。
- 載入到記憶體條中的應用都是根據記憶體地址去讀,那麼如果通過一些外掛載入到記憶體,然後可以在遍歷記憶體條中地址訪問到其他應用的內容,就導致賬號被盜等安全問題,所以這個方式也是不安全
的
- 怎麼解決這些問題呢?工程師們發現載入到記憶體中的應用,大多數之用到一小部分功能,這就導致了資源的浪費,於是就產生了懶載入
。懶載入是引用載入到記憶體中時,先載入啟動相關的,後面要用到就再載入到記憶體條。
這樣雖然會減少記憶體的佔用,但是應用接下來的記憶體不知道要分配到哪,這樣就導致程式碼不連續
,需要在執行過程不斷計算地址很不方便
,而且效率很低
,於是工程師們就創造了虛擬表
,也就引出了虛擬記憶體
虛擬記憶體
- 有了虛擬表,應用程式就只讀程式碼,而程式碼計算地址的事情交給
CPU
和硬體MMU
,MMU
是記憶體管理單元
,它只做一件事:翻譯地址
:
- 這樣應用程式的在執行時訪問的記憶體就是連續的,而訪問的記憶體就是虛擬記憶體
,虛擬記憶體對應的就是計算好的實體地址。
- 虛擬地址和實體地址不是
一個位元組一個位元組對印的,這樣效率就很低。由於現在的應用記憶體是一塊一塊的,乾脆就以塊為單位去對印,單位就是page
,此刻就產生了記憶體分頁管理的概念
對映表(記憶體分頁)
- 頁的大小在不同的作業系統中是不一樣的。在
iOS
中(64位)
一頁是16K
,在MAC
中是4K
。在MAC
中可以使用環境變數PAGESIZE
檢視:
- 現在
記憶體連續
的問題得以解決,安全問題
也解決了。由於一個應用只訪問自己的虛擬表,而翻譯出來的實體地址也是固定的,所以那些外掛就無法訪問其它應用。記憶體溢位
的問題也解決了,因為現在記憶體都是一頁一頁的訪問,所以就不會產生溢位。 - 當應用載入時,首先啟動需要的程式碼會直接加到記憶體,當要執行新的程式碼時,
cpu
發現這段程式碼記憶體中沒有就會把程式碼卡住,然後作業系統會將這頁加到實體記憶體中,這個現象就叫做缺頁中斷(pagefault)
。 - 作業系統需要執行的一頁程式碼載入到實體記憶體中時,會往空缺處插入。但手機啟動後,實體記憶體就沒有空位了,裡面都放了其它的一些資料,但到底往哪裡加呢,這個由作業系統決定。作業系統提供一個演算法:
頁面置換演算法
,它會覆蓋掉
不那麼活躍的部分。所以有時候多開啟些應用後,再去進入第一次開啟的應用時會重新啟動。 - 有時候在訪問記憶體時會訪問比
App
當前大的記憶體,因為虛擬表有8G
大小,能訪問的有4G
,那麼他會訪問到其它應用嗎?不會,這塊虛擬記憶體是沒有資料,它指向NULL
:
rebase/binding
-
binding
是繫結,當內部檔案訪問外部方法,就要通過內部符號繫結訪問外部,現在的繫結方式都是懶載入
-
rebase
是重定位。由於虛擬記憶體產生後,每次記憶體都是從0
開始,這個時候相應的安全就不存在了,所以作業系統就出現了一個新技術ASLR
:讓每次生成的虛擬頁面,不要從0開始,從隨機的值開始,應用的每次啟動的起始位置都是隨機的。此時行數的位置就成了ASLR+OFFSET
,也就是rebase
重定向 -
一頁的缺頁中斷是感知不到的,時間是毫秒級別,基本感知不到。但同時有大量的缺頁中斷,這個時候使用者就能感知到了,冷啟動時就會產生大量的缺頁異常。如下面例子:
- 重簽名一個
微信7.0.8
的ipa
,然後跑到自己的工程。執行起來後Cmd+i
開啟instruments
,然後選擇System Trace
,搜尋Main Thread
,之後選擇Virtual Memory
File Backed Page In
可以看到缺頁中斷的個數以及所用的時間:
- 通過觀察發現消耗的總時間中,
PageFault
佔絕大部份 - 再啟動一次發現耗時要少的多,此時應用的實體記憶體還沒被覆蓋,所以啟動會時間會少很多
冷啟動
:應用在實體記憶體中沒有佔用時的啟動是冷啟動。例如第一次開啟App
,或者APP
被殺死後,一段時間過後再開啟,都是冷啟動-
熱啟動
:應用編譯在實體記憶體中的記憶體還存在的啟動就是熱啟動,例如短時間APP
從後臺返回,APP
殺死後立即開啟,此時它的實體記憶體還存在,此時就是熱啟動。 -
如果減少
PageFault
的個數,就會達到優化的目的,具體操作下篇文章再進行講解