App啟動(dòng)優(yōu)化 - 實(shí)踐一

內(nèi)容概要:
1. 啟動(dòng)速度
2. 如何測(cè)量啟動(dòng)時(shí)間
3. 影響啟動(dòng)時(shí)間的原因
4. 啟動(dòng)優(yōu)化的方案

我們的應(yīng)用在運(yùn)行前應(yīng)該減少操作壤短,推遲一些啟動(dòng)行為,從而在啟動(dòng)前一點(diǎn)點(diǎn)時(shí)間進(jìn)行初始化雁社。下面讓我們來(lái)看本章內(nèi)容概要浴井。

一、啟動(dòng)速度

在不同平臺(tái)上霉撵,應(yīng)用的啟動(dòng)時(shí)間有所不同磺浙。蘋(píng)果在開(kāi)發(fā)者大會(huì)中提出400毫秒是一個(gè)不錯(cuò)的啟動(dòng)時(shí)間。原因在于徒坡,當(dāng)你看著應(yīng)用在運(yùn)行時(shí)撕氧,手機(jī)上的啟動(dòng)動(dòng)畫(huà)
能夠給用戶帶來(lái)一種在主屏幕和應(yīng)用之間切換時(shí)的持續(xù)感。這些動(dòng)畫(huà)占用時(shí)間喇完,并且會(huì)給你一個(gè)機(jī)會(huì)隱藏啟動(dòng)時(shí)間伦泥。
顯然根據(jù)情況會(huì)有所不同,app擴(kuò)展程序也是應(yīng)用啟動(dòng)的一部分锦溪,它們啟動(dòng)的時(shí)間不同不脯。手機(jī),電視和手表是不同的設(shè)備刻诊,但400毫秒是一個(gè)很好的啟動(dòng)目標(biāo)防楷。此外,啟動(dòng)時(shí)間不要超過(guò)20秒则涯,如果超過(guò)20秒复局,OS會(huì)終止應(yīng)用,以為它進(jìn)入了死循環(huán)粟判。
最后在所支持的最慢的設(shè)備上進(jìn)行測(cè)試也很重要亿昏,在Apple平臺(tái)支持的所有設(shè)備上,這些時(shí)間都是常量值浮入。如果你在iPhone 6s上測(cè)試的結(jié)果達(dá)到400毫秒龙优,很可能在iPhone 5上達(dá)不到。在前面的理論部分我們知道啟動(dòng)時(shí)需要做什么,要解析圖像彤断、映射圖像野舶、重設(shè)基址圖像、綁定圖像宰衙、啟動(dòng)圖像初始化器平道、調(diào)用主函數(shù),還有一些操作供炼,包括運(yùn)行框架初始化器以及加載NIB一屋,最終在應(yīng)用委托里收到回調(diào)。最后兩個(gè)操作也計(jì)算在我們前面說(shuō)的400毫秒的時(shí)間里袋哼。
啟動(dòng)應(yīng)用時(shí)冀墨,分冷啟動(dòng)和熱啟動(dòng)。

  • 熱啟動(dòng)是指啟動(dòng)時(shí)應(yīng)用已經(jīng)在內(nèi)存里涛贯,或者因?yàn)閱?dòng)過(guò)诽嘉,之前退出了,但還在內(nèi)核的磁盤(pán)緩存里弟翘,或者因?yàn)槟銊偘阉鼜?fù)制過(guò)去虫腋。
  • 冷啟動(dòng)是指啟動(dòng)時(shí)應(yīng)用不在磁盤(pán)緩存里。

測(cè)量冷啟動(dòng)時(shí)間通常更為重要稀余。冷啟動(dòng)時(shí)間更為中重要的原因是悦冀,當(dāng)用戶重啟手機(jī)后啟動(dòng)應(yīng)用,或很長(zhǎng)時(shí)間后啟動(dòng)應(yīng)用睛琳,這時(shí)非常需要一個(gè)快速啟動(dòng)盒蟆。為了測(cè)量冷啟動(dòng)時(shí)間,必須在每次測(cè)量之間重啟設(shè)備掸掏。如果你正嘗試優(yōu)化熱啟動(dòng)時(shí)間茁影,那冷啟動(dòng)時(shí)間應(yīng)該也會(huì)隨之加快。你可以通過(guò)加速開(kāi)發(fā)周期加快熱啟動(dòng)丧凤,但是請(qǐng)時(shí)不時(shí)地測(cè)試一下冷啟動(dòng)募闲。

二、如何測(cè)量啟動(dòng)時(shí)間

在主函數(shù)啟動(dòng)之前該如何測(cè)量時(shí)間愿待?dyld里有內(nèi)置的測(cè)量系統(tǒng)浩螺,可以通過(guò)設(shè)置環(huán)境變量DYLD_PRINT_STATISTICSDYLD_PRINT_STATISTICS_DETAILS訪問(wèn)。安裝操作系統(tǒng)時(shí)候就可用了仍侥,但它打印了很多內(nèi)部調(diào)試信息要出,并沒(méi)有什么用。它缺少了某些你可能想知道的信息∨┰ǎ現(xiàn)在我們就來(lái)改進(jìn)患蹂,在新的OS里進(jìn)步顯著。
它可以為你提供更為相關(guān)的信息,這些信息應(yīng)該會(huì)提供可操作的方法传于,來(lái)加快啟動(dòng)時(shí)間囱挑。當(dāng)加載每一個(gè)dylib,調(diào)試程序必須暫停啟動(dòng)才能解析應(yīng)用的符號(hào)和加載斷點(diǎn)沼溜,這通過(guò)USB線將非常耗時(shí)平挑。但是dyld清楚這一點(diǎn),它把調(diào)試時(shí)間從注冊(cè)時(shí)間里減出去系草,所以不必為此擔(dān)心通熄。但是你會(huì)注意到它,因?yàn)?code>dyld會(huì)顯示比你在鐘表中所觀察到的數(shù)字精細(xì)得多找都。這是預(yù)期的和能夠接受的唇辨,如果你看到了那個(gè)數(shù)字,一切都是正確的檐嚣。這里只是提示下助泽。
在Xcode里設(shè)置環(huán)境變量,如下圖所示:

image_00

運(yùn)行后嚎京,控制臺(tái)的輸出信息如下:

Total pre-main time: 10.6 seconds (100.0%)
         dylib loading time:  240.09 milliseconds (2.2%)
        rebase/binding time:  351.29 milliseconds (3.3%)
            ObjC setup time:  11.83 milliseconds (0.1%)
           initializer time:  10 seconds (94.3%)
       slowest intializers :
              MyAwesomeApp :  10.0 seconds (94.2%)

下面的時(shí)間條代表上面不同部分所占時(shí)間,而白色的虛線代表400毫秒:

image_01

上面的基本步驟就是前面理論部分講的啟動(dòng)順序隐解。

三鞍帝、優(yōu)化方案

(一)dylib加載優(yōu)化

關(guān)于dylib加載,還有從中看到的速度緩慢煞茫,需特別了解的是嵌入式dyld會(huì)非常昂貴帕涌。我們知道一個(gè)應(yīng)用大概包含100到400個(gè)Dylib,但是操作系統(tǒng)的dylib很快续徽,這是因?yàn)闃?gòu)建操作系統(tǒng)時(shí)蚓曼,我們預(yù)計(jì)算了大量dylib的數(shù)據(jù)。但是我們?cè)陂_(kāi)發(fā)操作系統(tǒng)時(shí)钦扭,無(wú)法做到每個(gè)應(yīng)用里的每個(gè)dylib纫版。我們無(wú)法預(yù)計(jì)算你要嵌入應(yīng)用的dylib,所以加載時(shí)必須要經(jīng)過(guò)一個(gè)耗時(shí)的過(guò)程客情。其解決方案是少用dylib其弊,而這將非常困難。這并不是說(shuō)完全不能使用膀斋,有很多方法可以合并已有的dylib梭伐。
可以使用靜態(tài)存檔,把dylib用這種方法鏈接到應(yīng)用仰担。還可以使用延遲加載次伶,也就是使用dlopen()函數(shù)。但是dlopen()函數(shù)會(huì)帶來(lái)細(xì)微的性能和正確性的問(wèn)題卷要,實(shí)際上會(huì)導(dǎo)致之后做更多的工作量,而這些工作量被延遲執(zhí)行了铃将。所以這是一個(gè)可行的選項(xiàng),但是必須要仔細(xì)思考清楚哑梳,盡量減少這種延遲加載的操作劲阎。

優(yōu)化方案:

  1. 使用更少的dylib;
  2. 合并現(xiàn)有的dylib鸠真;
  3. 使用靜態(tài)存檔悯仙;
  4. 懶加載;
image_02
(二)重設(shè)基址和綁定優(yōu)化

重設(shè)基址和綁定需要350毫秒時(shí)間吠卷,根據(jù)前面的理論部分我們知道锡垄,重設(shè)基址由于I/O會(huì)更慢一些,而綁定在計(jì)算上會(huì)昂貴祭隔,但它已經(jīng)完成I/O货岭。所以I/O是為了它們,它們混合在一起疾渴,時(shí)間也混合在一起千贯。我們深入研究一下,就會(huì)發(fā)現(xiàn)時(shí)間消耗在修復(fù)DATA段里的指針搞坝。所以我們必須減少指針的修復(fù)搔谴。用其他工具可以看到在DATA,分區(qū)桩撮,dyld信息中修復(fù)的指針敦第。還能顯示正在哪些段和分區(qū)操作,你會(huì)很清楚地了解到在修復(fù)什么店量。比如芜果,若看到一個(gè)在ObjC分區(qū)的ObjC類符號(hào),很可能你有很多ObjC類融师。所以你能做的一件事就是減少ObjC類對(duì)象和ivars的數(shù)量右钾。有很多編碼樣式都鼓勵(lì)只有一個(gè)或兩個(gè)函數(shù)的小類,這些特殊的模式可能會(huì)導(dǎo)致速度逐漸變慢诬滩。當(dāng)你越加越多時(shí)霹粥,更要格外小心。現(xiàn)在有100或者1000個(gè)類不成問(wèn)題疼鸟,但有些大型應(yīng)用有上萬(wàn)個(gè)類后控。在這種情況下,將會(huì)消耗更多的啟動(dòng)時(shí)間空镜,因?yàn)閮?nèi)核要把它們讀入頁(yè)面浩淘。
還可以做一件事情捌朴,可以嘗試減少使用C++虛擬函數(shù)。虛擬函數(shù)將會(huì)創(chuàng)建我們稱之為V表格的東西张抄,這和ObjC元數(shù)據(jù)相同砂蔽,因?yàn)樗鼈冊(cè)?code>DATA段創(chuàng)建了必須修復(fù)的結(jié)構(gòu)。它們比ObjC元數(shù)據(jù)小署惯,但它們對(duì)于某些應(yīng)用程序來(lái)說(shuō)仍然很重要左驾。
還可以使用Swift的結(jié)構(gòu)體。因?yàn)镾wift通常使用更少這種帶有指針修復(fù)的數(shù)據(jù)极谊。并且Swift更為內(nèi)聯(lián)诡右,可以更好的使用code-gen減少消耗。所以轉(zhuǎn)為Swift語(yǔ)言也是一個(gè)好方法轻猖。
還有一點(diǎn)帆吻,需要小心機(jī)器生成的代碼。曾經(jīng)有過(guò)這樣的例子咙边,你可能用DSL或一些自定義語(yǔ)言描述某個(gè)結(jié)構(gòu)猜煮,然后有一個(gè)程序從中生成其他代碼。如果這些程序中有很多指針败许,它們將變得非常昂貴王带,因?yàn)樯纱a時(shí)會(huì)生成非常非常大的結(jié)構(gòu)。也有生成兆量級(jí)數(shù)據(jù)的情況檐束。但好處是比較容易進(jìn)行控制辫秧,因?yàn)槟阒恍枰淖兇a生成器,使其使用非指針的內(nèi)容被丧,比如偏移基址,結(jié)構(gòu)绪妹。

優(yōu)化方案:

  • 減少__DATA指針甥桂;
  • 減少ObjC元數(shù)據(jù) - 類,選擇器和類別邮旷;
  • 減少C++虛擬函數(shù)黄选;
  • 使用Swift結(jié)構(gòu);
  • 檢查機(jī)器生成的代碼 - 使用偏移量而非指針婶肩,標(biāo)記為只讀办陷;
image_03
(三)ObjC Setup優(yōu)化

關(guān)于設(shè)置ObjC,前面理論部分講過(guò)它做的工作律歼,它要處理類的注冊(cè)民镜,要處理非脆弱ivar,要處理分類的注冊(cè)险毁,還要讓選擇器唯一制圈。這里我們不用處理太多们童,因?yàn)檫@些問(wèn)題通過(guò)之前對(duì)重設(shè)基址,數(shù)據(jù)鲸鹦,和綁定的修復(fù)時(shí)都已經(jīng)解決慧库,之前所做的減少和在這里做的完全相同。

image_04

(四)初始化器的優(yōu)化

初始化器有兩種類型馋嗜,顯示初始化器齐板,比如+load,前面理論部分建議用+initiailize取代它葛菇,這將導(dǎo)致ObjC運(yùn)行時(shí)在類被實(shí)例化而不是文件被加載時(shí)初始化代碼甘磨。
或者在C/C++里,有一個(gè)可以放在函數(shù)上的屬性熟呛,可以讓函數(shù)像初始化器一樣生成代碼宽档,因此這是顯示初始化器,但不建議這么做庵朝。建議選擇調(diào)用site initializers取代上面的方式吗冤。調(diào)用site initializers是指調(diào)用像dispatch_once()函數(shù),或者在跨平臺(tái)代碼里的pthread_once()九府,或者在C++代碼中的std::once()椎瘟。所有這些函數(shù)基本上都有相同的功能,這些函數(shù)的代碼只會(huì)在第一次點(diǎn)擊時(shí)運(yùn)行一次侄旬。dispatch_once()在系統(tǒng)里很優(yōu)秀肺蔚,第一次執(zhí)行以后,幾乎等同于無(wú)操作儡羔,直接跳過(guò)宣羊。所以強(qiáng)烈建議不要使用顯示初始化器。
另一種初始化器是隱式初始化器汰蜘。隱式初始化器大部分來(lái)自帶有非默認(rèn)初始化器仇冯,非默認(rèn)構(gòu)造函數(shù)的C++全局變量∽宀伲可以選擇調(diào)用site initializers取代它苛坚,在很多地方可以把全局變量替換成想要初始化的無(wú)全局結(jié)構(gòu)或指針的對(duì)象。還有一種選擇是沒(méi)有非默認(rèn)初始化器色难。所以在C++中泼舱,初始化器稱為POD,一個(gè)普通的舊數(shù)據(jù)枷莉。
如果對(duì)象只是普通的舊數(shù)據(jù)娇昙,靜態(tài)鏈接器,或者靜態(tài)鏈接器將會(huì)為DATA分區(qū)預(yù)計(jì)算所有的數(shù)據(jù)依沮,只把數(shù)據(jù)放在那里涯贞,不一定要運(yùn)行枪狂,不一定要修復(fù)。最后一點(diǎn)宋渔,很難找到它州疾,因?yàn)樗鼈兪请[性的,但是編譯器會(huì)收到警告 --- -Wglobal-constructors,如果這么做皇拣,只要產(chǎn)生其中一個(gè)严蓖,就會(huì)有警告。所以把它添加到編譯器使用的標(biāo)志里是個(gè)好方法氧急。還有一個(gè)選擇就是使用Swift重新編寫(xiě)颗胡。理由就是Swift有全局變量,并且會(huì)被初始化吩坝,它們確保在使用前被初始化毒姨,但是其方法不是用初始化器,在后臺(tái)使用一次dispatch_once()钉寝。所以轉(zhuǎn)為Swift將會(huì)做到這一點(diǎn)弧呐。
最后在初始化器里請(qǐng)不要調(diào)用dlopen()函數(shù),它將會(huì)帶來(lái)巨大的性能問(wèn)題嵌纲。原因有很多俘枫,dyld在運(yùn)行時(shí),是在應(yīng)用啟動(dòng)之前逮走,我們可以做一些諸如關(guān)閉鎖的操作鸠蚪,因?yàn)槭菃尉€程。當(dāng)dlopen()出現(xiàn)在那種情況下师溅,初始化器的運(yùn)行發(fā)生了改變茅信,可能會(huì)有多線程,必須要打開(kāi)鎖墓臭,將會(huì)帶來(lái)巨大的性能下降汹押,還會(huì)帶來(lái)細(xì)微的死鎖和未定義的行為。還有起便,不要在初始化器上開(kāi)啟線程,也是出于同樣的理由窖维。

九榆综、參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市铸史,隨后出現(xiàn)的幾起案子鼻疮,更是在濱河造成了極大的恐慌,老刑警劉巖琳轿,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件判沟,死亡現(xiàn)場(chǎng)離奇詭異耿芹,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)挪哄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)吧秕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人迹炼,你說(shuō)我怎么就攤上這事砸彬。” “怎么了斯入?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵砂碉,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我刻两,道長(zhǎng)增蹭,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任磅摹,我火速辦了婚禮滋迈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘偏瓤。我一直安慰自己杀怠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布厅克。 她就那樣靜靜地躺著赔退,像睡著了一般。 火紅的嫁衣襯著肌膚如雪证舟。 梳的紋絲不亂的頭發(fā)上硕旗,一...
    開(kāi)封第一講書(shū)人閱讀 49,821評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音女责,去河邊找鬼漆枚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛抵知,可吹牛的內(nèi)容都是我干的墙基。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼刷喜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼残制!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起掖疮,我...
    開(kāi)封第一講書(shū)人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤初茶,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后浊闪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體恼布,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡螺戳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了折汞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倔幼。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖字支,靈堂內(nèi)的尸體忽然破棺而出凤藏,到底是詐尸還是另有隱情,我是刑警寧澤堕伪,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布揖庄,位于F島的核電站,受9級(jí)特大地震影響欠雌,放射性物質(zhì)發(fā)生泄漏蹄梢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一富俄、第九天 我趴在偏房一處隱蔽的房頂上張望禁炒。 院中可真熱鬧,春花似錦霍比、人聲如沸幕袱。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)们豌。三九已至,卻和暖如春浅妆,著一層夾襖步出監(jiān)牢的瞬間望迎,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工凌外, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辩尊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓康辑,卻偏偏與公主長(zhǎng)得像摄欲,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子疮薇,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容