第二七節(jié)課-啟動(dòng)優(yōu)化

1. 啟動(dòng)時(shí)間檢測

這節(jié)課主要針對的是:

  • 冷啟動(dòng):在內(nèi)存中不包含相關(guān)的數(shù)據(jù),必須從磁盤載入到內(nèi)存中,這是由系統(tǒng)決定的,但是當(dāng)APP的內(nèi)存被覆蓋的時(shí)候,APP也能決定

對應(yīng)的熱啟動(dòng)則是:殺掉進(jìn)程以后,數(shù)據(jù)仍然在,啟動(dòng)APP的時(shí)候不用重新加載數(shù)據(jù)

  • 測試APP啟動(dòng)以 main 函數(shù)為分界線,main 函數(shù)之前的監(jiān)測一定要靠系統(tǒng)反饋,main 函數(shù)以后的則可以監(jiān)測,這節(jié)課主要針對 main 函數(shù)之前的優(yōu)化,因?yàn)?main 函數(shù)之后的操作是不統(tǒng)一的,根據(jù)業(yè)務(wù)來寫的。

如果想要了解 main 函數(shù)之前的耗時(shí),是可以通過以下配置來監(jiān)測,iOS APP的啟動(dòng)流程可以通過在xcode的Edit Scheme ->Run->Arguments -> Environment Variables中加入環(huán)境變量DYLD_PRINT_STATISTICS打印出來:

配置.png

運(yùn)行APP,打印結(jié)果如下:

Total pre-main time:  31.70 milliseconds (100.0%)
         dylib loading time:  27.29 milliseconds (86.0%)
        rebase/binding time: 411015771.5 seconds (61552300.3%)
            ObjC setup time:  78.12 milliseconds (246.4%)
           initializer time:  74.97 milliseconds (236.4%)
           slowest intializers :
             libSystem.B.dylib :   6.10 milliseconds (19.2%)
   libBacktraceRecording.dylib :   7.03 milliseconds (22.1%)
               libobjc.A.dylib :   1.69 milliseconds (5.3%)
    libMainThreadChecker.dylib :  57.82 milliseconds (182.3%)

從結(jié)果可以看出四個(gè)階段的名稱以及它們對應(yīng)的耗時(shí)。

dylib loading 階段是動(dòng)態(tài)鏈接庫dylib加載動(dòng)態(tài)庫的階段,包括系統(tǒng)動(dòng)態(tài)庫和我們自己的動(dòng)態(tài)庫。
rebase/binding這個(gè)階段實(shí)際就是兩個(gè)操作rebase和binding。rebase就是內(nèi)部符號(hào)偏移修正,binding是外部符號(hào)綁定。
ObjC setup這一階段主要是OC類相關(guān)的事務(wù),比如類的注冊,category、protocol的讀取等等。
intializers 程序的初始化,包括所依賴的動(dòng)態(tài)庫的初始化。在這期間會(huì)調(diào)用 Objc 類的 + load 函數(shù),調(diào)用 C++ 中帶有constructor 標(biāo)記的函數(shù)等。

上面打印出的這四個(gè)階段還是比較粗略的,實(shí)際上dyld在啟動(dòng)過程中是比較復(fù)雜的。如果將上面的DYLD_PRINT_STATISTICS換成DYLD_PRINT_STATISTICS_DETAILS,打印得會(huì)更加詳細(xì):

total time: 1.3 seconds (100.0%)
  total images loaded:  398 (392 from dyld shared cache)
  total segments mapped: 21, into 415 pages
  total images loading time: 944.18 milliseconds (68.5%)
  total load time in ObjC:  80.88 milliseconds (5.8%)
  total debugger pause time: 801.87 milliseconds (58.2%)
  total dtrace DOF registration time:   0.00 milliseconds (0.0%)
  total rebase fixups:  16,279
  total rebase fixups time:   7.47 milliseconds (0.5%)
  total binding fixups: 567,346

  total binding fixups time: 275.57 milliseconds (20.0%)
  total weak binding fixups time:   0.03 milliseconds (0.0%)
  total redo shared cached bindings time: 459.30 milliseconds (33.3%)
  total bindings lazily fixed up: 0 of 0
  total time in initializers and ObjC +load:  68.67 milliseconds (4.9%)
                         libSystem.B.dylib :   6.04 milliseconds (0.4%)
               libBacktraceRecording.dylib :   7.01 milliseconds (0.5%)
                libMainThreadChecker.dylib :  52.05 milliseconds (3.7%)
total symbol trie searches:    1378030
total symbol table binary searches:    0
total images defining weak symbols:  50
total images using weak symbols:  110

main 函數(shù)之后的優(yōu)化有幾個(gè)點(diǎn):

  • 懶加載
  • 刪除不再使用的代碼,即便那些代碼不在使用,但在它們?nèi)匀粎⑴c到了編譯中,因此也會(huì)消耗一部分時(shí)間
  • 首屏頁面最好不要使用 XibStoryBoard,因?yàn)樗鼈儽旧砭托枰馕?/li>
2. 啟動(dòng)優(yōu)化:二進(jìn)制重排

二進(jìn)制重排原理:虛擬內(nèi)存和物理內(nèi)存,早期的計(jì)算機(jī)只有物理內(nèi)存,內(nèi)存條的地址叫做物理地址,早期的數(shù)據(jù)訪問都是通過物理地址來訪問的,這樣子有個(gè)問題就是拿到內(nèi)存地址就可以對數(shù)據(jù)進(jìn)行修改,因此存在數(shù)據(jù)問題和內(nèi)存不夠用的問題。操作系統(tǒng)發(fā)現(xiàn)軟件雖然很大,但是實(shí)際上用戶只使用了部分活躍功能,所以把應(yīng)用的全部數(shù)據(jù)載入內(nèi)存有點(diǎn)浪費(fèi),因此虛擬內(nèi)存就出現(xiàn)了,虛擬內(nèi)存就是讓應(yīng)用覺得自己被全部載入了內(nèi)存,實(shí)際上并沒有,每個(gè)應(yīng)用都有一個(gè)虛擬的內(nèi)存,cpu首先訪問虛擬地址,虛擬地址和物理地址會(huì)有一個(gè)對應(yīng)

物理內(nèi)存 & 虛擬內(nèi)存

物理內(nèi)存:指的是通過物理內(nèi)存條獲得的內(nèi)存空間。
虛擬內(nèi)存:跟物理內(nèi)存相反,虛擬內(nèi)存指的一種計(jì)算機(jī)系統(tǒng)內(nèi)存管理技術(shù),它使得應(yīng)用程序認(rèn)為它擁有連續(xù)可用的內(nèi)存,實(shí)際上它通常被分隔成多個(gè)物理內(nèi)存碎片。
只談概念太空洞,下面我們用圖來解釋什么是物理內(nèi)存、什么是虛擬內(nèi)存。
物理內(nèi)存的概述圖如下:

物理和虛擬內(nèi)存.png

分析:
在沒有虛擬內(nèi)存的概念之前,每個(gè)應(yīng)用一啟動(dòng),操作系統(tǒng)就會(huì)把整個(gè)應(yīng)用放進(jìn)物理內(nèi)存里面

物理內(nèi)存存在的問題:

  • 內(nèi)存緊張 - 由于每次都是直接把整個(gè)應(yīng)用放進(jìn)物理內(nèi)存里面,很可能出現(xiàn)內(nèi)存不夠用的情況。
  • 進(jìn)程安全 - 每個(gè)進(jìn)程之間的物理地址是連續(xù)的,可以拿到別的進(jìn)程的地址,容易出現(xiàn)進(jìn)程不安全的問題。
    虛擬內(nèi)存的概述圖如下:
虛擬內(nèi)存.png

分析:
虛擬內(nèi)存的技術(shù)出現(xiàn)之后,每個(gè)進(jìn)程并不是直接全部扔進(jìn)物理內(nèi)存,而是給每個(gè)應(yīng)用分配一個(gè)虛擬的內(nèi)存,虛擬內(nèi)存通過虛擬頁表來把相應(yīng)數(shù)據(jù)放進(jìn)物理內(nèi)存里面。

虛擬內(nèi)存的技術(shù)出現(xiàn)之后,也有了內(nèi)存分頁的概念,虛擬頁表把一個(gè)進(jìn)程分成若干頁,比如:Page1、Page2、Page3......,當(dāng)啟動(dòng)進(jìn)程1的時(shí)候,只需要把Page1裝載進(jìn)物理內(nèi)存,以此類推,如上圖。
PS:大家注意上圖的顏色區(qū)分能夠很好的理解虛擬內(nèi)存相關(guān)概念。

虛擬內(nèi)存解決了物理內(nèi)存存在的兩個(gè)問題,由于每個(gè)進(jìn)程的數(shù)據(jù)被分成了很多頁裝載進(jìn)內(nèi)存,不會(huì)再出現(xiàn)內(nèi)存緊張的問題,而且一個(gè)應(yīng)用在物理內(nèi)存里面的地址是不連續(xù)的,所以無法訪問到別的進(jìn)程的地址,也保證了線程安全。
注意:雖然一個(gè)應(yīng)用在物理內(nèi)存中的內(nèi)存是不連續(xù)的,但是訪問數(shù)據(jù)的時(shí)候是連續(xù)的,原因就在于我們訪問數(shù)據(jù)訪問的是對應(yīng)的虛擬映射表,這個(gè)虛擬映射表記錄的某個(gè)方法、函數(shù)對應(yīng)在物理內(nèi)存的真實(shí)地址。

缺頁中斷(PageFault)

在上面我們說過在有了虛擬內(nèi)存技術(shù)之后,內(nèi)存以分頁的形式裝載進(jìn)物理內(nèi)存里面,比如我們現(xiàn)在啟動(dòng)一個(gè)應(yīng)用只需要先把啟動(dòng)相關(guān)的內(nèi)存Page1到Page10裝載進(jìn)物理內(nèi)存,當(dāng)用到A功能的時(shí)候發(fā)現(xiàn)數(shù)據(jù)沒有在物理內(nèi)存里面,此時(shí)操作系統(tǒng)會(huì)阻塞APP進(jìn)程觸發(fā)一次PageFault,操作系統(tǒng)會(huì)從磁盤中讀取相應(yīng)的數(shù)據(jù)到物理內(nèi)存上,再將其映射到虛擬內(nèi)存頁表上面。PageFault耗時(shí)非常短,平均在0.5ms左右,所以用戶一般感知不到,但是有一種情況可能會(huì)造成大量的PageFault,那就是啟動(dòng)時(shí)刻,所以我們就可以思考如何減少啟動(dòng)時(shí)的PageFault次數(shù),從而優(yōu)化啟動(dòng)時(shí)長。

二進(jìn)制重排

在分析二進(jìn)制重排之前,我們先了解一下 Link Map File 是個(gè)什么東西。

鏈接映射文件

鏈接映射文件:Link Map File,里面記錄的是每個(gè)類所生成的可執(zhí)行文件的路徑、CPU架構(gòu)、符號(hào)等信息,可以簡單的理解為這個(gè)文件告訴了我們一個(gè)應(yīng)用的可執(zhí)行文件的排列順序。

BuildSetting - Write Link Map File設(shè)置為YES。

如下圖所示:

linkmap設(shè)置.png

編譯項(xiàng)目之后根據(jù)上圖的地址找到我們需要的 Link Map 文件,如圖所示:

linkmap文件.png

linkmap 文件內(nèi)方法的排列順序遵循兩種規(guī)則:

  1. 文件的編譯順序,即:先按照項(xiàng)目 - Build Phases - Compile Sources中的順序排列,如下圖所示:
編譯順序.png
  1. 同一個(gè)文件內(nèi)部則遵循從上至下的方法書寫順序排列

這樣的排列順序就導(dǎo)致:啟動(dòng)時(shí)刻的代碼順序分別分布在了不同的文件里面,即啟動(dòng)代碼并沒有在一起,導(dǎo)致啟動(dòng)時(shí)刻有大量的 pageFault

優(yōu)化思路:將所有啟動(dòng)時(shí)刻需要調(diào)用的方法排列在一起,這就是二進(jìn)制重排。

缺頁中斷個(gè)數(shù)

在進(jìn)行優(yōu)化之前,我們首先需要檢查一下優(yōu)化之前的缺頁中斷個(gè)數(shù),打開 Instruments,選擇 System Trace,運(yùn)行之后。分析數(shù)據(jù)如圖,選擇“Main Thread”,底部的File Backed Page In即為缺頁中斷個(gè)數(shù)。

所下圖所示

systemTrace.png

當(dāng)我們測試過一次后再打開 System Trace,發(fā)現(xiàn)缺頁中斷個(gè)數(shù)少了很多,這個(gè)主要是由于緩存造成的,要想完完整整地去測試?yán)鋯?dòng)的缺頁中斷次數(shù)的話,可以在殺死app之后再打開幾個(gè)其他的APP,然后再過個(gè)一兩分鐘之后,再啟動(dòng)這個(gè)APP的話,就應(yīng)該是冷啟動(dòng)了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容