www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當(dāng)前位置:首頁 > 公眾號(hào)精選 > 架構(gòu)師社區(qū)
[導(dǎo)讀]來自:高德技術(shù) 前言 最近高德地圖APP完成了一次啟動(dòng)優(yōu)化專項(xiàng),超預(yù)期將雙端啟動(dòng)的耗時(shí)都降低了65%以上,iOS在iPhone7上速度達(dá)到了400毫秒以內(nèi)。就像產(chǎn)品們用后說的,快到不習(xí)慣。算一下每天為用戶省下的時(shí)間,還是蠻有成就感的,本文做個(gè)小結(jié)。 (文中配圖均

高德APP啟動(dòng)耗時(shí)剖析與優(yōu)化實(shí)踐(iOS篇)

來自:高德技術(shù)

前言

最近高德地圖APP完成了一次啟動(dòng)優(yōu)化專項(xiàng),超預(yù)期將雙端啟動(dòng)的耗時(shí)都降低了65%以上,iOS在iPhone7上速度達(dá)到了400毫秒以內(nèi)。就像產(chǎn)品們用后說的,快到不習(xí)慣。算一下每天為用戶省下的時(shí)間,還是蠻有成就感的,本文做個(gè)小結(jié)。

高德APP啟動(dòng)耗時(shí)剖析與優(yōu)化實(shí)踐(iOS篇)


高德APP啟動(dòng)耗時(shí)剖析與優(yōu)化實(shí)踐(iOS篇)

(文中配圖均為多才多藝的技術(shù)哥哥手繪)


啟動(dòng)階段性能多維度分析

要優(yōu)化,首先要做到的是對(duì)啟動(dòng)階段的各個(gè)性能緯度做分析,包括主線程耗時(shí)、CPU、內(nèi)存、I/O、網(wǎng)絡(luò)。這樣才能更加全面的掌握啟動(dòng)階段的開銷,找出不合理的方法調(diào)用。


啟動(dòng)越快,更多的方法調(diào)用就應(yīng)該做成按需執(zhí)行,將啟動(dòng)壓力分?jǐn)?,只留下那些啟?dòng)后方法都會(huì)依賴的方法和庫的初始化,比如網(wǎng)絡(luò)庫、Crash庫等。而剩下那些需要預(yù)加載的功能可以放到啟動(dòng)階段后再執(zhí)行。


啟動(dòng)有哪幾種類型,有哪些階段呢?


啟動(dòng)類型分為

  • Cold:APP重啟后啟動(dòng),不在內(nèi)存里也沒有進(jìn)程存在。

  • Warm:APP最近結(jié)束后再啟動(dòng),有部分在內(nèi)存但沒有進(jìn)程存在。

  • Resume:APP沒結(jié)束,只是暫停,全在內(nèi)存中,進(jìn)程也存在。


分析階段一般都是針對(duì)Cold類型進(jìn)行分析,目的就是要讓測試環(huán)境穩(wěn)定。為了穩(wěn)定測試環(huán)境,有時(shí)還需要找些穩(wěn)定的機(jī)型,對(duì)于iOS來說iPhone7性能中等,穩(wěn)定性也不錯(cuò)就很適合,Android的Vivo系列也相對(duì)穩(wěn)定,華為和小米系列數(shù)據(jù)波動(dòng)就比較大。


除了機(jī)型外,控制測試機(jī)溫度也很重要,一旦溫度過高系統(tǒng)還會(huì)降頻執(zhí)行,影響測試數(shù)據(jù)。有時(shí)候還會(huì)設(shè)置飛行模式采用Mock網(wǎng)絡(luò)請(qǐng)求的方式來減少不穩(wěn)定的網(wǎng)絡(luò)影響測試數(shù)據(jù)。最好是重啟后退iCloud賬號(hào),放置一段時(shí)間再測,更加準(zhǔn)確些。


了解啟動(dòng)階段的目的就是聚焦范圍,從用戶體驗(yàn)上來確定哪個(gè)階段要快,以便能夠讓用戶可視和響應(yīng)用戶操作的時(shí)間更快。


簡單來說iOS啟動(dòng)分為加載Mach-O和運(yùn)行時(shí)初始化過程,加載Mach-O會(huì)先判斷加載的文件是不是Mach-O,通過文件第一個(gè)字節(jié),也叫魔數(shù)來判斷,當(dāng)是下面四種時(shí)可以判定是Mach-O文件:


  • 0xfeedface對(duì)應(yīng)的loader.h里的宏是MH_MAGIC

  • 0xfeedfact宏是MH_MAGIC_64

  • NXSwapInt(MH_MAGIC)宏MH_GIGAM

  • NXSwapInt(MH_MAGIC_64)宏MH_GIGAM_64


Mach-O主要分為:

  • 中間對(duì)象文件(MH_OBJECT)

  • 可執(zhí)行二進(jìn)制(MH_EXECUTE)

  • VM 共享庫文件(MH_FVMLIB)

  • Crash 產(chǎn)生的Core文件(MH_CORE)

  • preload(MH_PRELOAD)

  • 動(dòng)態(tài)共享庫(MH_DYLIB)

  • 動(dòng)態(tài)鏈接器(MH_DYLINKER)

  • 靜態(tài)鏈接文件(MH_DYLIB_STUB)符號(hào)文件和調(diào)試信息(MH_DSYM)這幾種。


確定是Mach-O后,內(nèi)核會(huì)fork一個(gè)進(jìn)程,execve開始加載。檢查Mach-O Header。隨后加載dyld和程序到Load Command地址空間。通過 dyld_stub_binder開始執(zhí)行dyld,dyld會(huì)進(jìn)行rebase、binding、lazy binding、導(dǎo)出符號(hào),也可以通過DYLD_INSERT_LIBRARIES進(jìn)行hook。


dyld_stub_binder給偏移量到dyld解釋特殊字節(jié)碼Segment中,也就是真實(shí)地址,把真實(shí)地址寫入到la_symbol_ptr里,跳轉(zhuǎn)時(shí)通過stub的jump指令跳轉(zhuǎn)到真實(shí)地址。dyld加載所有依賴庫,將動(dòng)態(tài)庫導(dǎo)出的trie結(jié)構(gòu)符號(hào)執(zhí)行符號(hào)綁定,也就是non lazybinding,綁定解析其他模塊功能和數(shù)據(jù)引用過程,就是導(dǎo)入符號(hào)。


Trie也叫數(shù)字樹或前綴樹,是一種搜索樹。查找復(fù)雜度O(m),m是字符串的長度。和散列表相比,散列最差復(fù)雜度是O(N),一般都是 O(1),用 O(m)時(shí)間評(píng)估 hash。散列缺點(diǎn)是會(huì)分配一大塊內(nèi)存,內(nèi)容越多所占內(nèi)存越大。Trie不僅查找快,插入和刪除都很快,適合存儲(chǔ)預(yù)測性文本或自動(dòng)完成詞典。


為了進(jìn)一步優(yōu)化所占空間,可以將Trie這種樹形的確定性有限自動(dòng)機(jī)壓縮成確定性非循環(huán)有限狀態(tài)自動(dòng)體(DAFSA),其空間小,做法是會(huì)壓縮相同分支。


對(duì)于更大內(nèi)容,還可以做更進(jìn)一步的優(yōu)化,比如使用字母縮減的實(shí)現(xiàn)技術(shù),把原來的字符串重新解釋為較長的字符串;使用單鏈?zhǔn)搅斜恚?jié)點(diǎn)設(shè)計(jì)為由符號(hào)、子節(jié)點(diǎn)、下一個(gè)節(jié)點(diǎn)來表示;將字母表數(shù)組存儲(chǔ)為代表ASCII字母表的256位的位圖。


盡管Trie對(duì)于性能會(huì)做很多優(yōu)化,但是符號(hào)過多依然會(huì)增加性能消耗,對(duì)于動(dòng)態(tài)庫導(dǎo)出的符號(hào)不宜太多,盡量保持公共符號(hào)少,私有符號(hào)集豐富。這樣維護(hù)起來也方便,版本兼容性也好,還能優(yōu)化動(dòng)態(tài)加載程序到進(jìn)程的時(shí)間。


然后執(zhí)行attribute的constructor函數(shù)。舉個(gè)例子:

#include <stdio.h>
__attribute__((constructor))static void prepare() { printf("%s\n", "prepare");}
__attribute__((destructor))static void end() { printf("%s\n", "end");}
void showHeader() { printf("%s\n", "header");}

運(yùn)行結(jié)果:

ming@mingdeMacBook-Pro macho_demo % ./main "hi"preparehiend

運(yùn)行時(shí)初始化過程分為:

  • 加載類擴(kuò)展。

  • 加載C++靜態(tài)對(duì)象。

  • 調(diào)用+load函數(shù)。

  • 執(zhí)行main函數(shù)。

  • Application初始化,到applicationDidFinishLaunchingWithOptions執(zhí)行完。

  • 初始化幀渲染,到viewDidAppear執(zhí)行完,用戶可見可操作。

高德APP啟動(dòng)耗時(shí)剖析與優(yōu)化實(shí)踐(iOS篇)

也就是說對(duì)啟動(dòng)階段的分析以viewDidAppear為截止。這次優(yōu)化之前已經(jīng)對(duì)Application初始化之前做過優(yōu)化,效果并不明顯,沒有本質(zhì)的提高,所以這次主要針對(duì)Application初始化到viewDidAppear這個(gè)階段各個(gè)性能多緯度進(jìn)行分析。


工具的選擇其實(shí)目前看來是很多的,Apple提供的System Trace會(huì)提供全面系統(tǒng)的行為,可以顯示底層系統(tǒng)線程和內(nèi)存調(diào)度情況,分析鎖、線程、內(nèi)存、系統(tǒng)調(diào)用等問題??偟膩碚f,通過System Trace能清楚知道每時(shí)每刻APP對(duì)系統(tǒng)資源的使用情況。


System Trace能查看線程的狀態(tài),可以了解高優(yōu)線程使用相對(duì)于CPU數(shù)量是否合理,可以看到線程在執(zhí)行、掛起、上下文切換、被打斷還是被搶占的情況。虛擬內(nèi)存使用產(chǎn)生的耗時(shí)也能看到,比如分配物理內(nèi)存,內(nèi)存解壓縮,無緩存時(shí)進(jìn)行緩存的耗時(shí)等。甚至是發(fā)熱情況也能看到。


System Trace還提供手動(dòng)打點(diǎn)進(jìn)行信息顯式,在你的代碼中導(dǎo)入sys/kdebug_signpost.h后,配對(duì)kdebug_signpost_start和kdebug_signpost_end就可以了。這兩個(gè)方法有五個(gè)參數(shù),第一個(gè)是id,最后一個(gè)是顏色,中間都是預(yù)留字段。


Xcode11開始XCTest還提供了測量性能的Api。蘋果在2019年WWDC啟動(dòng)優(yōu)化專題:

https://developer.apple.com/videos/play/wwdc2019/423/

也介紹了Instruments里的最新模板App launch如何分析啟動(dòng)性能。但是要想達(dá)到對(duì)啟動(dòng)數(shù)據(jù)進(jìn)行留存取均值、Diff、過濾、關(guān)聯(lián)分析等自動(dòng)化操作,App launch目前還沒法做到。


下面針對(duì)主線程耗時(shí)、CPU、網(wǎng)絡(luò)、內(nèi)存、I/O 等多維度進(jìn)行分析:

高德APP啟動(dòng)耗時(shí)剖析與優(yōu)化實(shí)踐(iOS篇)

  • 主線程耗時(shí)

多個(gè)緯度性能分析中最重要、最終用戶體感到的是主線程耗時(shí)分析。對(duì)主線程方法耗時(shí)可以直接使用Massier,這是everettjf開發(fā)的一個(gè)Objective-C方法跟蹤工具:

https://everettjf.github.io/2019/05/06/messier/

生成trace json進(jìn)行分析,或者參看這個(gè)代碼

GCDFetchFeed/SMCallTraceCore.c at master · ming1016/GCDFetchFeed · GitHub

https://github.com/ming1016/GCDFetchFeed/blob/master/GCDFetchFeed/GCDFetchFeed/Lib/SMLagMonitor/SMCallTraceCore.c

自己手動(dòng)hook objc_msgSend生成一份Objective-C方法耗時(shí)數(shù)據(jù)進(jìn)行分析。還有種插樁方式,可以解析IR(加快編譯速度),然后在每個(gè)方法前后插入耗時(shí)統(tǒng)計(jì)函數(shù)。


文章后面我會(huì)著重介紹如何開發(fā)工具進(jìn)一步分析這份數(shù)據(jù),以達(dá)到監(jiān)控啟動(dòng)階段方法耗時(shí)的目的。


hook所有的方法調(diào)用,對(duì)詳細(xì)分析時(shí)很有用,不過對(duì)于整個(gè)啟動(dòng)時(shí)間影響很大,要想獲取啟動(dòng)每個(gè)階段更準(zhǔn)確的時(shí)間消耗還需要依賴手動(dòng)埋點(diǎn)。


為了更好的分析啟動(dòng)耗時(shí)問題,手動(dòng)埋點(diǎn)也會(huì)埋的越來越多,也會(huì)影響啟動(dòng)時(shí)間精確度,特別是當(dāng)團(tuán)隊(duì)很多,模塊很多時(shí),問題會(huì)突出。但是每個(gè)團(tuán)隊(duì)在排查啟動(dòng)耗時(shí)往往只會(huì)關(guān)注自己或相關(guān)某幾個(gè)模塊的分析,基于此,可以把不同模塊埋點(diǎn)分組,靈活組合,這樣就可以照顧到多種需求了。


  • CPU

為什么分析啟動(dòng)慢除了分析主線程方法耗時(shí)外,還要分析其它緯度的性能呢?


我們先看看啟動(dòng)慢的表現(xiàn),啟動(dòng)慢意味著界面響應(yīng)慢、網(wǎng)絡(luò)慢(數(shù)據(jù)量大、請(qǐng)求數(shù)多)、CPU超負(fù)荷降頻(并行任務(wù)多、運(yùn)算多),可以看出影響啟動(dòng)的因素很多,還需要全面考慮。


對(duì)于CPU來說,WWDC的 

What’s New in Energy Debugging - WWDC 2018 - Videos - Apple Developer 

https://developer.apple.com/videos/play/wwdc2018/228/

介紹了用Energy Log來查CPU耗電,當(dāng)前臺(tái)三分鐘或后臺(tái)一分鐘CPU線程連續(xù)占用80%以上就判定為耗電,同時(shí)記錄耗電線程堆棧供分析。還有一個(gè)MetrickKit專門用來收集電源和性能統(tǒng)計(jì)數(shù)據(jù),每24小時(shí)就會(huì)對(duì)收集的數(shù)據(jù)進(jìn)行匯總上報(bào),Mattt在NShipster網(wǎng)站上也發(fā)了篇文章專門進(jìn)行介紹:

https://nshipster.com/metrickit/


那么,CPU的詳細(xì)使用情況如何獲取呢?也就是說哪個(gè)方法用了多少CPU。


有好幾種獲取詳細(xì)CPU使用情況的方法。線程是計(jì)算機(jī)資源調(diào)度和分配的基本單位。CPU使用情況會(huì)提現(xiàn)到線程這樣的基本單位上。task_theads的act_list數(shù)組包含所有線程,使用thread_info的接口可以返回線程的基本信息,這些信息定義在thread_basic_info_t結(jié)構(gòu)體中。這個(gè)結(jié)構(gòu)體內(nèi)的信息包含了線程運(yùn)行時(shí)間、運(yùn)行狀態(tài)以及調(diào)度優(yōu)先級(jí),其中也包含了CPU使用信息cpu_usage。


獲取方式參看:

objective c - Get detailed iOS CPU usage with different states - Stack Overflow

https://stackoverflow.com/questions/43866416/get-detailed-ios-cpu-usage-with-different-states


GT GitHub - Tencent/GT

https://github.com/Tencent/GT

也有獲取CPU的代碼。


整體CPU占用率可以通過host_statistics函數(shù)取到host_cpu_load_info,其中cpu_ticks數(shù)組是CPU運(yùn)行的時(shí)鐘脈沖數(shù)量。通過cpu_ticks數(shù)組里的狀態(tài),可以分別獲取CPU_STATE_USER、CPU_STATE_NICE、CPU_STATE_SYSTEM這三個(gè)表示使用中的狀態(tài),除以整體CPU就可以取到CPU的占比。


通過NSProcessInfo的activeProcessorCount還可以得到CPU的核數(shù)。線上數(shù)據(jù)分析時(shí)會(huì)發(fā)現(xiàn)相同機(jī)型和系統(tǒng)的手機(jī),性能表現(xiàn)卻截然不同,這是由于手機(jī)過熱或者電池?fù)p耗過大后系統(tǒng)降低了CPU頻率所致。


所以,如果取得CPU頻率后也可以針對(duì)那些降頻的手機(jī)來進(jìn)行針對(duì)性的優(yōu)化,以保證流暢體驗(yàn)。獲取方式可以參考:

https://github.com/zenny-chen/CPU-Dasher-for-iOS


  • 內(nèi)存

要想獲取APP真實(shí)的內(nèi)存使用情況可以參看WebKit的源碼:

https://github.com/WebKit/webkit/blob/52bc6f0a96a062cb0eb76e9a81497183dc87c268/Source/WTF/wtf/cocoa/MemoryFootprintCocoa.cpp


JetSam會(huì)判斷APP使用內(nèi)存情況,超出閾值就會(huì)殺死APP,JetSam獲取閾值的代碼在這里:

https://github.com/apple/darwin-xnu/blob/0a798f6738bc1db01281fc08ae024145e84df927/bsd/kern/kern_memorystatus.c


整個(gè)設(shè)備物理內(nèi)存大小可以通過NSProcessInfo的physicalMemory來獲取。


  • 網(wǎng)絡(luò)

對(duì)于網(wǎng)絡(luò)監(jiān)控可以使用Fishhook這樣的工具Hook網(wǎng)絡(luò)底層庫CFNetwork。網(wǎng)絡(luò)的情況比較復(fù)雜,所以需要定些和時(shí)間相關(guān)的關(guān)鍵的指標(biāo),指標(biāo)如下:

  • DNS時(shí)間

  • SSL時(shí)間

  • 首包時(shí)間

  • 響應(yīng)時(shí)間


有了這些指標(biāo)才能夠有助于更好的分析網(wǎng)絡(luò)問題。啟動(dòng)階段的網(wǎng)絡(luò)請(qǐng)求是非常多的,所以HTTP的性能是非常要注意的。以下是WWDC網(wǎng)絡(luò)相關(guān)的Session:

Your App and Next Generation Networks - WWDC 2015 - Videos - Apple Developer

https://developer.apple.com/videos/play/wwdc2015/719/

Networking with NSURLSession - WWDC 2015 - Videos - Apple Developer

https://developer.apple.com/videos/play/wwdc2015/711/

Networking for the Modern Internet - WWDC 2016 - Videos - Apple Developer

https://developer.apple.com/videos/play/wwdc2016/714/

Advances in Networking, Part 1 - WWDC 2017 - Videos - Apple Developer

https://developer.apple.com/videos/play/wwdc2017/707/

Advances in Networking, Part 2 - WWDC 2017 - Videos - Apple Developer

https://developer.apple.com/videos/play/wwdc2017/709/

Optimizing Your App for Today’s Internet - WWDC 2018 - Videos - Apple Developer

https://developer.apple.com/videos/play/wwdc2018/714/


  • I/O

對(duì)于I/O可以使用

Frida ? A world-class dynamic instrumentation framework | Inject JavaScript to explore native apps on Windows, macOS, GNU/Linux, iOS, Android, and QNX

https://www.frida.re/

這種動(dòng)態(tài)二進(jìn)制插樁技術(shù),在程序運(yùn)行時(shí)去插入自定義代碼獲取I/O的耗時(shí)和處理的數(shù)據(jù)大小等數(shù)據(jù)。Frida還能夠在其它平臺(tái)使用。


關(guān)于多維度分析更多的資料可以看看歷屆WWDC的介紹。下面我列下16年來 WWDC關(guān)于啟動(dòng)優(yōu)化的Session,每場都很精彩。

Using Time Profiler in Instruments - WWDC 2016 - Videos - Apple Developer

https://developer.apple.com/videos/play/wwdc2016/418/

Optimizing I/O for Performance and Battery Life - WWDC 2016 - Videos - Apple Developer

https://developer.apple.com/videos/play/wwdc2016/719/

Optimizing App Startup Time - WWDC 2016 - Videos - Apple Developer

https://developer.apple.com/videos/play/wwdc2016/406/

App Startup Time: Past, Present, and Future - WWDC 2017 - Videos - Apple Developer

https://developer.apple.com/videos/play/wwdc2017/413/

Practical Approaches to Great App Performance - WWDC 2018 - Videos - Apple Developer

https://developer.apple.com/videos/play/wwdc2018/407/

Optimizing App Launch - WWDC 2019 - Videos - Apple Developer

https://developer.apple.com/videos/play/wwdc2019/423/


延后任務(wù)管理

高德APP啟動(dòng)耗時(shí)剖析與優(yōu)化實(shí)踐(iOS篇)

經(jīng)過前面所說的對(duì)主線程耗時(shí)方法和各個(gè)緯度性能分析后,對(duì)于那些分析出來沒必要在啟動(dòng)階段執(zhí)行的方法,可以做成按需或延后執(zhí)行。


任務(wù)延后的處理不能粗獷的一口氣在啟動(dòng)完成后在主線程一起執(zhí)行,那樣用戶僅僅只是看到了頁面,依然沒法響應(yīng)操作。那該怎么做呢?套路一般是這樣,創(chuàng)建四個(gè)隊(duì)列,分別是:

  • 異步串行隊(duì)列

  • 異步并行隊(duì)列

  • 閑時(shí)主線程串行隊(duì)列

  • 閑時(shí)異步串行隊(duì)列


有依賴關(guān)系的任務(wù)可以放到異步串行隊(duì)列中執(zhí)行。異步并行隊(duì)列可以分組執(zhí)行,比如使用dispatch_group,然后對(duì)每組任務(wù)數(shù)量進(jìn)行限制,避免CPU、線程和內(nèi)存瞬時(shí)激增影響主線程用戶操作,定義有限數(shù)量的串行隊(duì)列,每個(gè)串行隊(duì)列做特定的事情,這樣也能夠避免性能消耗短時(shí)間突然暴漲引起無法響應(yīng)用戶操作。使用dispatch_semaphore_t在信號(hào)量阻塞主隊(duì)列時(shí)容易出現(xiàn)優(yōu)先級(jí)反轉(zhuǎn),需要減少使用,確保QoS傳播。可以用dispatch group替代,性能一樣,功能不差。異步編程可以直接GCD接口來寫,也可以使用阿里的協(xié)程框架

coobjc GitHub - alibaba/coobjc

https://github.com/alibaba/coobjc


閑時(shí)隊(duì)列實(shí)現(xiàn)方式是監(jiān)聽主線程runloop狀態(tài),在kCFRunLoopBeforeWaiting時(shí)開始執(zhí)行閑時(shí)隊(duì)列里的任務(wù),在kCFRunLoopAfterWaiting時(shí)停止。


優(yōu)化后如何保持?

攻易守難,就像剛到新團(tuán)隊(duì)時(shí)將包大小減少了48兆,但是一年多一直能夠守住,除了決心還需要有手段。對(duì)于啟動(dòng)優(yōu)化來說,將各個(gè)性能緯度通過監(jiān)控的方式盯住是必要的,但是發(fā)現(xiàn)問題后快速、便捷的定位到問題還是需要找些突破口。我的思路是將啟動(dòng)階段方法耗時(shí)多的按照時(shí)間線一條一條排出來,每條包括方法名、方法層級(jí)、所屬類、所屬模塊、維護(hù)人??紤]到便捷性,最好還能方便的查看方法代碼內(nèi)容。


接下來我通過開發(fā)一個(gè)工具,詳細(xì)介紹下怎么實(shí)現(xiàn)這樣的效果。


  • 解析json

如前面所說在輸出一份Chrome trace規(guī)范的方法耗時(shí)json后,先要解析這份數(shù)據(jù)。這份json數(shù)據(jù)類似下面的樣子:

{"name":"[SMVeilweaa]upVeilState:","cat":"catname","ph":"B","pid":2381,"tid":0,"ts":21},{"name":"[SMVeilweaa]tatLaunchState:","cat":"catname","ph":"B","pid":2381,"tid":0,"ts":4557},{"name":"[SMVeilweaa]tatTimeStamp:state:","cat":"catname","ph":"B","pid":2381,"tid":0,"ts":4686},{"name":"[SMVeilweaa]tatTimeStamp:state:","cat":"catname","ph":"E","pid":2381,"tid":0,"ts":4727},{"name":"[SMVeilweaa]tatLaunchState:","cat":"catname","ph":"E","pid":2381,"tid":0,"ts":5732},{"name":"[SMVeilweaa]upVeilState:","cat":"catname","ph":"E","pid":2381,"tid":0,"ts":5815},

通過Chrome的Trace-Viewer可以生成一個(gè)火焰圖。其中name字段包含了類、方法和參數(shù)的信息,cat字段可以加入其它性能數(shù)據(jù),ph為B表示方法開始,為E表示方法結(jié)束,ts字段表示。


很多工程在啟動(dòng)階段會(huì)執(zhí)行大量方法,很多方法耗時(shí)很少,可以過濾那些小于10毫秒的方法,讓分析更加聚焦。

高德APP啟動(dòng)耗時(shí)剖析與優(yōu)化實(shí)踐(iOS篇)

耗時(shí)的高低也做了顏色的區(qū)分。外部耗時(shí)指的是子方法以外系統(tǒng)或沒源碼的三方方法的耗時(shí),規(guī)則是父方法調(diào)用的耗時(shí)減去其子方法總耗時(shí)。


目前為止通過過濾耗時(shí)少的方法調(diào)用,可以更容易發(fā)現(xiàn)問題方法。但是,有些方法單次執(zhí)行耗時(shí)不多,但是會(huì)執(zhí)行很多次,累加耗時(shí)會(huì)大,這樣的情況也需要體現(xiàn)在展示頁面里。另外外部耗時(shí)高時(shí)或者碰到自己不了解的方法時(shí),是需要到工程源碼里去搜索對(duì)應(yīng)的方法源碼進(jìn)行分析的,有的方法名很通用時(shí)還需要花大量時(shí)間去過濾無用信息。


因此接下來還需要做兩件事情,首先累加方法調(diào)用次數(shù)和耗時(shí),體現(xiàn)在展示頁面中,另一個(gè)是從工程中獲取方法源碼能夠在展示頁面中進(jìn)行點(diǎn)擊顯示。


完整思路如下圖:

高德APP啟動(dòng)耗時(shí)剖析與優(yōu)化實(shí)踐(iOS篇)

  • 展示方法源碼

在頁面上展示源碼需要先解析.xcworkspace文件,通過.xcworkspace文件取到工程里所有的.xcodeproj文件。分析.xcodeproj文件取到所有.m和.mm源碼文件路徑,解析源碼,取到方法的源碼內(nèi)容進(jìn)行展示。


解析.xcworkspace

開.xcworkspace,可以看到這個(gè)包內(nèi)主要文件是contents.xcworkspacedata。內(nèi)容是一個(gè)xml:

<?xml version="1.0" encoding="UTF-8"?><Workspace version = "1.0"> <FileRef location = "group:GCDFetchFeed.xcodeproj"> </FileRef> <FileRef location = "group:Pods/Pods.xcodeproj"> </FileRef></Workspace>


高德APP啟動(dòng)耗時(shí)剖析與優(yōu)化實(shí)踐(iOS篇)

解析.xcodeproj

高德APP啟動(dòng)耗時(shí)剖析與優(yōu)化實(shí)踐(iOS篇)

通過XML的解析可以獲取FileRef節(jié)點(diǎn)內(nèi)容,xcodeproj的文件路徑就在FileRef節(jié)點(diǎn)的location屬性里。每個(gè)xcodeproj文件里會(huì)有project工程的源碼文件。為了能夠獲取方法的源碼進(jìn)行展示,那么就先要取出所有project工程里包含的源文件的路徑。


xcodeproj的文件內(nèi)容看起來大概是下面的樣子。

高德APP啟動(dòng)耗時(shí)剖析與優(yōu)化實(shí)踐(iOS篇)

其實(shí)內(nèi)容還有很多,需要一個(gè)個(gè)解析出來。


考慮到xcodeproj里的注釋很多,也都很有用,因此會(huì)多設(shè)計(jì)些結(jié)構(gòu)來保存值和注釋。思路是根據(jù)XcodeprojNode的類型來判斷下一級(jí)是key value結(jié)構(gòu)還是array結(jié)構(gòu)。如果XcodeprojNode的類型是dicStart表示下級(jí)是key value結(jié)構(gòu)。如果類型是arrStart就是array結(jié)構(gòu)。當(dāng)碰到類型是dicEnd,同時(shí)和最初dicStart是同級(jí)時(shí),遞歸下一級(jí)樹結(jié)構(gòu)。而arrEnd不用遞歸,xcodeproj里的array只有值類型的數(shù)據(jù)。


有了基本節(jié)點(diǎn)樹結(jié)構(gòu)以后就可以設(shè)計(jì)xcodeproj里各個(gè)section的結(jié)構(gòu)。主要有以下的section:

  • PBXBuildFile:文件,最終會(huì)關(guān)聯(lián)到PBXFileReference。

  • PBXContainerItemProxy:部署的元素。

  • PBXFileReference:各類文件,有源碼、資源、庫等文件。

  • PBXFrameworksBuildPhase:用于framework的構(gòu)建。

  • PBXGroup:文件夾,可嵌套,里面包含了文件與文件夾的關(guān)系。

  • PBXNativeTarget:Target的設(shè)置。

  • PBXProject:Project的設(shè)置,有編譯工程所需信息。

  • PBXResourcesBuildPhase:編譯資源文件,有xib、storyboard、plist以及圖片等資源文件。

  • PBXSourcesBuildPhase:編譯源文件(.m)。

  • PBXTargetDependency:Taget的依賴。

  • PBXVariantGroup:.storyboard文件。

  • XCBuildConfiguration:Xcode編譯配置,對(duì)應(yīng)Xcode的Build Setting面板內(nèi)容。

  • XCConfigurationList:構(gòu)建配置相關(guān),包含項(xiàng)目文件和target文件。


得到section結(jié)構(gòu)Xcodeproj后,就可以開始分析所有源文件的路徑了。根據(jù)前面列出的section的說明,PBXGroup包含了所有文件夾和文件的關(guān)系,Xcodeproj的pbxGroup字段的key是文件夾,值是文件集合,因此可以設(shè)計(jì)一個(gè)結(jié)構(gòu)體XcodeprojSourceNode用來存儲(chǔ)文件夾和文件關(guān)系。


接下來需要取得完整的文件路徑。通過recusiveFatherPaths函數(shù)獲取文件夾路徑。這里需要注意的是需要處理 ../ 這種文件夾路徑符。


解析.m .mm文件

高德APP啟動(dòng)耗時(shí)剖析與優(yōu)化實(shí)踐(iOS篇)

對(duì)Objective-C解析可以參考LLVM,這里只需要找到每個(gè)方法對(duì)應(yīng)的源碼,所以自己也可以實(shí)現(xiàn)。分詞前先看看LLVM是怎么定義token的。定義文件在這里:

https://opensource.apple.com/source/lldb/lldb-69/llvm/tools/clang/include/clang/Basic/TokenKinds.def

根據(jù)這個(gè)定義我設(shè)計(jì)了token的結(jié)構(gòu)體,主體部分如下:

// 切割符號(hào) [](){}.&=*+-<>~!/%^|?:;,#@public enum OCTK { case unknown // 不是 token case eof // 文件結(jié)束 case eod // 行結(jié)束 case codeCompletion // Code completion marker case cxxDefaultargEnd // C++ default argument end marker case comment // 注釋 case identifier // 比如 abcde123 case numericConstant(OCTkNumericConstant) // 整型、浮點(diǎn) 0x123,解釋計(jì)算時(shí)用,分析代碼時(shí)可不用 case charConstant // ‘a(chǎn)’ case stringLiteral // “foo” case wideStringLiteral // L”foo” case angleStringLiteral // <foo> 待處理需要考慮作為小于符號(hào)的問題
// 標(biāo)準(zhǔn)定義部分 // 標(biāo)點(diǎn)符號(hào) case punctuators(OCTkPunctuators)
// 關(guān)鍵字 case keyword(OCTKKeyword)
// @關(guān)鍵字 case atKeyword(OCTKAtKeyword)}

完整的定義在這里:

MethodTraceAnalyze/ParseOCTokensDefine.swift

https://github.com/ming1016/MethodTraceAnalyze/blob/master/MethodTraceAnalyze/OC/ParseOCTokensDefine.swift

分詞過程可以參看LLVM的實(shí)現(xiàn):

clang: lib/Lex/Lexer.cpp Source File

http://clang.llvm.org/doxygen/Lexer_8cpp_source.html

我在處理分詞時(shí)主要是按照分隔符一一對(duì)應(yīng)處理,針對(duì)代碼注釋和字符串進(jìn)行了特殊處理,一個(gè)注釋一個(gè)token,一個(gè)完整字符串一個(gè)token。我分詞實(shí)現(xiàn)代碼:

MethodTraceAnalyze/ParseOCTokens.swift

https://github.com/ming1016/MethodTraceAnalyze/blob/master/MethodTraceAnalyze/OC/ParseOCTokens.swift


由于只要取到類名和方法里的源碼,所以語法分析時(shí),只需要對(duì)類定義和方法定義做解析就可以,語法樹中節(jié)點(diǎn)設(shè)計(jì):

// OC 語法樹節(jié)點(diǎn)public struct OCNode { public var type: OCNodeType public var subNodes: [OCNode] public var identifier: String // 標(biāo)識(shí) public var lineRange: (Int,Int) // 行范圍 public var source: String // 對(duì)應(yīng)代碼}// 節(jié)點(diǎn)類型public enum OCNodeType { case `default` case root case `import` case `class` case method}

其中l(wèi)ineRange記錄了方法所在文件的行范圍,這樣就能夠從文件中取出代碼,并記錄在source字段中。


解析語法樹需要先定義好解析過程的不同狀態(tài):

private enum RState { case normal case eod // 換行 case methodStart // 方法開始 case methodReturnEnd // 方法返回類型結(jié)束 case methodNameEnd // 方法名結(jié)束 case methodParamStart // 方法參數(shù)開始 case methodContentStart // 方法內(nèi)容開始 case methodParamTypeStart // 方法參數(shù)類型開始 case methodParamTypeEnd // 方法參數(shù)類型結(jié)束 case methodParamEnd // 方法參數(shù)結(jié)束 case methodParamNameEnd // 方法參數(shù)名結(jié)束
case at // @ case atImplementation // @implementation
case normalBlock // oc方法外部的 block {},用于 c 方法}

完整解析出方法所屬類、方法行范圍的代碼在這里:

MethodTraceAnalyze/ParseOCNodes.swift

https://github.com/ming1016/MethodTraceAnalyze/blob/master/MethodTraceAnalyze/OC/ParseOCNodes.swift


解析.m和.mm文件,一個(gè)一個(gè)串行解的話,對(duì)于大工程,每次解的速度很難接受,所以采用并行方式去讀取解析多個(gè)文件。經(jīng)過測試,發(fā)現(xiàn)每組在60個(gè)以上時(shí)能夠最大利用我機(jī)器(2.5 GHz雙核Intel Core i7)的CPU,內(nèi)存占用只有60M,一萬多.m文件的工程大概2分半能解完。


使用的是dispatch group的wait,保證并行的一組完成再進(jìn)入下一組。

高德APP啟動(dòng)耗時(shí)剖析與優(yōu)化實(shí)踐(iOS篇)

現(xiàn)在有了每個(gè)方法對(duì)應(yīng)的源碼,接下來就可以和前面trace的方法對(duì)應(yīng)上。頁面展示只需要寫段js就能夠控制點(diǎn)擊時(shí)展示對(duì)應(yīng)方法的源碼。


頁面展示

在進(jìn)行HTML頁面展示前,需要將代碼里的換行和空格替換成HTML里的對(duì)應(yīng)的和&nbsp;。

let allNodes = ParseOC.ocNodes(workspacePath: “/Users/ming/Downloads/GCDFetchFeed/GCDFetchFeed/GCDFetchFeed.xcworkspace”)var sourceDic = [String:String]()for aNode in allNodes { sourceDic[aNode.identifier] = aNode.source.replacingOccurrences(of: “\n”, with: “</br>”).replacingOccurrences(of: “ “, with: “&nbsp;”)}

用p標(biāo)簽作為源碼展示的標(biāo)簽,方法執(zhí)行順序的編號(hào)加方法名作為p標(biāo)簽的id,然后用display: none; 將p標(biāo)簽隱藏。方法名用a標(biāo)簽,click屬性執(zhí)行一段js代碼,當(dāng)a標(biāo)簽點(diǎn)擊時(shí)能夠顯示方法對(duì)應(yīng)的代碼。這段js代碼如下:

function sourceShowHidden(sourceIdName) { var sourceCode = document.getElementById(sourceIdName); sourceCode.style.display = “block”;}

最終效果如下圖:

高德APP啟動(dòng)耗時(shí)剖析與優(yōu)化實(shí)踐(iOS篇)

將動(dòng)態(tài)分析和靜態(tài)分析進(jìn)行了結(jié)合,后面可以通過不同版本進(jìn)行對(duì)比,發(fā)現(xiàn)哪些方法的代碼實(shí)現(xiàn)改變了,能展示在頁面上。還可以進(jìn)一步靜態(tài)分析出哪些方法會(huì)調(diào)用到I/O函數(shù)、起新線程、新隊(duì)列等,然后展示到頁面上,方便分析。


讀到最后,可以看到這個(gè)方法分析工具并沒有用任何一個(gè)輪子,其實(shí)有些是可以使用現(xiàn)有輪子的,比如json、xml、xcodeproj、Objective-C語法分析等,之所以沒有用是因?yàn)椴煌喿邮褂玫恼Z言和技術(shù)區(qū)別較大,當(dāng)格式更新時(shí)如果使用的單個(gè)輪子沒有更新會(huì)影響整個(gè)工具。開發(fā)這個(gè)工具主要工作是在解析上,所以使用自有解析技術(shù)也能夠讓所做的功能更聚焦,不做沒用的功能,減少代碼維護(hù)量,所要解析格式更新后,也能夠自主去更新解析方式。更重要的一點(diǎn)是可以親手接觸下這些格式的語法設(shè)計(jì)。


結(jié)語

本文小結(jié)了啟動(dòng)優(yōu)化的技術(shù)手段,總的來說,對(duì)啟動(dòng)進(jìn)行優(yōu)化的決心的重要程度是遠(yuǎn)大于技術(shù)手段的,決定著是否能夠優(yōu)化的更多。技術(shù)手段有很多,我覺得手段的好壞區(qū)別只是在效率上,最差的情況全用手動(dòng)一個(gè)個(gè)去查耗時(shí)也是能夠解題的。


特別推薦一個(gè)分享架構(gòu)+算法的優(yōu)質(zhì)內(nèi)容,還沒關(guān)注的小伙伴,可以長按關(guān)注一下:

高德APP啟動(dòng)耗時(shí)剖析與優(yōu)化實(shí)踐(iOS篇)

長按訂閱更多精彩▼

高德APP啟動(dòng)耗時(shí)剖析與優(yōu)化實(shí)踐(iOS篇)

如有收獲,點(diǎn)個(gè)在看,誠摯感謝

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場,如有問題,請(qǐng)聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運(yùn)營商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡稱"軟通動(dòng)力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉