「原創(chuàng)譯文」iOS 性能優(yōu)化:Instruments 工具的救命三招

https://segmentfault.com/a/1190000002568993
「原創(chuàng)譯文」iOS 性能優(yōu)化:Instruments 工具的救命三招
你的 iOS 應(yīng)用蹂季,運(yùn)行速度靠譜嗎冕广?中槍的同學(xué)莫要愁,性能優(yōu)化咱有妙招偿洁。用 Xcode 自家的調(diào)試工具 Instruments撒汉,揪出那些堵線程、占內(nèi)存涕滋、耗資源的問題代碼睬辐,徹底破掉迷局,讓應(yīng)用揚(yáng)眉吐氣!

對于每位 iOS 開發(fā)者來說溯饵,代碼性能是個(gè)避不開的話題侵俗。隨著項(xiàng)目的擴(kuò)大和功能的增多,沒經(jīng)過認(rèn)真調(diào)試和優(yōu)化的代碼丰刊,要么任性地卡頓運(yùn)行坡慌,要么低調(diào)地崩潰了之……結(jié)果呢,大家用著不高興藻三,開發(fā)者也不開心。
其實(shí)要破這個(gè)局面并不難跪者,只要在 Xcode 自帶的監(jiān)控調(diào)試工具 Instruments 上花點(diǎn)功夫棵帽,讓大代碼流暢運(yùn)行也不是神話。Instruments 提供了很多功能渣玲,我會重點(diǎn)介紹一下我最常用的三大類:
Time Profiler:分析代碼的執(zhí)行時(shí)間逗概,找出導(dǎo)致程序變慢的原因。
Allocations:監(jiān)測內(nèi)存使用/分配情況迅速膨脹的內(nèi)存可以很快讓程序斃命忘衍,所以要多加防范逾苫。
Leaks:找到引發(fā)內(nèi)存泄漏的起點(diǎn)

即使有 ARC(自動引用計(jì)數(shù))內(nèi)存管理機(jī)制,但在現(xiàn)實(shí)中對象之間引用復(fù)雜枚钓,循環(huán)引用導(dǎo)致的內(nèi)存泄漏仍然難以避免铅搓,所以關(guān)鍵時(shí)刻還要自力更生。
針對這三方面的測試搀捷,我寫了個(gè)演示應(yīng)用星掰,放在 GitHub 上,來幫助大家更直觀地了解這些工具的使用方法嫩舟。好氢烘,進(jìn)入正題。


Time Profiler
時(shí)間都去哪兒啦家厌? Time Profiler 可以回答播玖。它會按照設(shè)定的時(shí)間間隔(默認(rèn) 1 毫秒)來跟蹤每一線程的堆棧信息(stack trace),并通過比較時(shí)間間隔之間的堆棧狀態(tài)饭于,來推算出某個(gè)方法執(zhí)行了多久蜀踏,給出一個(gè)近似值。在演示應(yīng)用頭一項(xiàng)「Time Profiler: System Methods」中镰绎,我用插入排序(Insertion Sort)和冒泡排序(Bubble Sort)兩種算法來做性能比較脓斩,下面是 Swift 代碼:
/* 引用自:http://waynewbishop.com/swift/sorting-algorithms/ */func insertionSort() { var x, y, key: Int for (x = 0; x < numberList.count; x++) { key = numberList[x] for (y = x; y > -1; y--) { if key < numberList[y] { numberList.removeAtIndex(y + 1) numberList.insert(key, atIndex: y) } } }}func bubbleSort() { var x, y, z, passes, key : Int for (x = 0; x < numberList.count; ++x) { passes = (numberList.count - 1) - x; for (y = 0; y < passes; y++) { key = numberList[y] if (key > numberList[y + 1]) { z = numberList[y + 1] numberList[y + 1] = key numberList[y] = z } } }}

這段代碼主要是對數(shù)組的添加和刪除,兩種方法執(zhí)行起來耗時(shí)不多畴栖,但后臺發(fā)生的系統(tǒng)動作卻多得讓人眼暈随静。


可以發(fā)現(xiàn),代碼用到了很多間接依賴,這些都是支撐代碼運(yùn)行的系統(tǒng)庫文件燎猛。因?yàn)樘幚泶髷?shù)據(jù)集比較消耗系統(tǒng)資源恋捆,所以要盡可能地把繁重的操作放到后臺去做,上面的代碼就走的后臺線程重绷。在上圖的 Call Tree 中可以看到沸停,被調(diào)用的堆棧名是 dispatch_worker_thread3。如果把它放到主線程去執(zhí)行昭卓,程序肯定會掛起愤钾。不信你注釋掉 dispatch_async 調(diào)用看一下。
再來個(gè)圖片加載的例子候醒。

這兒有三種圖片加載方法:
loadSlowImage1:從指定 URL 下載一張圖片(加載速度慢)
loadImage2:從本地資源庫加載一張圖片(注意:沒用系統(tǒng)緩存)
loadFastImage3:從系統(tǒng)緩存中加載一張圖片(加載速度快)

我們來看看 Time Profiler 算出的結(jié)果是不是跟預(yù)想的一樣能颁。
進(jìn)入演示應(yīng)用第二項(xiàng)「Time Profiler: Our Methods」,點(diǎn)擊「Reload」十次來重復(fù)加載圖片倒淫,這樣能產(chǎn)生足夠的數(shù)據(jù)來分析伙菊。然后在 Time Profiler 圖表中通過拖拉鼠標(biāo)選中要放大查看的區(qū)域,從 Call Tree 中雙擊調(diào)用了 .reload 方法那一行(上圖中加亮選中那一行)敌土,就會跳轉(zhuǎn)到對應(yīng)的代碼行镜硕,所用時(shí)間也標(biāo)注出來了。


看到誰最花時(shí)間了吧返干。雖然代碼沒什么可優(yōu)化的地方兴枯,但大家應(yīng)該認(rèn)識到緩存能發(fā)揮的作用。所以即使有時(shí)還得調(diào)用 loadSlowImage犬金,多數(shù)情況下把圖片緩存下來念恍,還是能省些資源占用。
此外晚顷,我想再說說 Call Tree 的選項(xiàng)設(shè)置峰伙。

這些選項(xiàng)默認(rèn)是不選的,但把它們勾選上可以幫你更快定位到關(guān)鍵的代碼上该默,往往這也是問題的源頭瞳氓。
Separate by Thread:按線程分開做分析,這樣更容易揪出那些吃資源的問題線程栓袖。特別是對于主線程匣摘,它要處理和渲染所有的接口數(shù)據(jù),一旦受到阻塞裹刮,程序必然卡頓或停止響應(yīng)音榜。
Invert Call Tree:反向輸出調(diào)用樹。把調(diào)用層級最深的方法顯示在最上面捧弃,更容易找到最耗時(shí)的操作赠叼。
Hide Missing Symbols:隱藏缺失符號擦囊。如果 dSYM 文件或其他系統(tǒng)架構(gòu)缺失,列表中會出現(xiàn)很多奇怪的十六進(jìn)制的數(shù)值嘴办,用此選項(xiàng)把這些干擾元素屏蔽掉瞬场,讓列表回歸清爽。
Hide System Libraries:隱藏系統(tǒng)庫文件涧郊。過濾掉各種系統(tǒng)調(diào)用贯被,只顯示自己的代碼調(diào)用。
Flattern Recursion:拼合遞歸妆艘。將同一遞歸函數(shù)產(chǎn)生的多條堆棧(因?yàn)檫f歸函數(shù)會調(diào)用自己)合并為一條彤灶。
Top Functions:找到最耗時(shí)的函數(shù)或方法。

需要添加其他工具的話:


Allocations
我們經(jīng)常需要從服務(wù)器下載大量圖片批旺,特別是開發(fā)照片類的應(yīng)用枢希。但往往稍不注意,內(nèi)存使用就會暴增朱沃,所以得保證把這些圖片緩存下來以便重復(fù)使用。下面來看看演示程序中內(nèi)存分配的例子茅诱。

從圖中可以看到逗物,每次點(diǎn)擊「Reload」重新載入圖片時(shí),內(nèi)存都會出現(xiàn)使用峰值瑟俭。應(yīng)用先分配大量內(nèi)存來替換原有圖片翎卓,然后再釋放掉這部分內(nèi)存,可想而知這樣的操作效率高不了摆寄,而且如果要下載更大的文件失暴,呃,局面大概會失控吧微饥。
看一下堆棧列表第四行逗扒,ImageIO_PNG_Data 里有 9 張?zhí)幱诨顒訝顟B(tài)的圖片,占用了12.38 MB 內(nèi)存欠橘,這些都是沒被系統(tǒng)釋放或緩存的內(nèi)存矩肩,所以導(dǎo)致堆內(nèi)存分配升高。接下來再看看使用緩存后的效果肃续。

使用了緩存庫(Swift Haneke)后黍檩,點(diǎn)「Reload」五次,這回在 Allocations 列表中卻看不到 ImageIO_PNG_Data 對象了始锚,這說明它是空的刽酱,沒有任何圖像數(shù)據(jù)。同時(shí)瞧捌,All Heap Allocations 的大小已從剛才的 14.61 MB 降到了 2.51 MB棵里。Anonymous VM(匿名虛擬內(nèi)存)是系統(tǒng)為程序預(yù)留的、可能會立即被重復(fù)使用的一部分可用內(nèi)存。要防止程序崩潰衍慎,就別讓堆的尺寸增長太快转唉。
還有就是,例子用的是異步方式來加載圖片稳捆,這樣用不著等到所有圖片下載完才能在界面中顯示赠法。大多數(shù)圖像緩存庫都會把加載工作放到后臺,以避免延長主線程的響應(yīng)周期乔夯。
All Heap Allocations 是程序真實(shí)的內(nèi)存分配情況砖织,All Anonymous VM則是系統(tǒng)為程序分配的虛擬內(nèi)存,為的就是當(dāng)程序有需要的時(shí)候末荐,能夠及時(shí)為程序提供足夠的內(nèi)存空間侧纯,而不會現(xiàn)用現(xiàn)創(chuàng)建。

Leaks
盡管 Apple 推出的 ARC 可以有效防范內(nèi)存泄漏甲脏,但出問題的機(jī)率還是會有眶熬,Swift 也不例外。鑒于篇幅有限块请,本文就不涉及內(nèi)存和 ARC 的工作原理了娜氏,具體可以參考官方文檔。我會用代碼來觸發(fā)內(nèi)存泄漏墩新。
首先從最底層上說贸弥,當(dāng)兩個(gè)對象相互建立了強(qiáng)引用(strong reference),當(dāng)一個(gè)對象被釋放海渊,另一個(gè)對象由于是強(qiáng)引用的關(guān)系不允許被釋放绵疲,此時(shí) ARC 無法確定沒被釋放的對象到底還有沒有用,于是就導(dǎo)致了內(nèi)存泄漏臣疑。


要解決這個(gè)問題盔憨,可以將其中的一個(gè)對象中變量設(shè)為 weak,不讓它出現(xiàn)在保留周期中讯沈。很多開發(fā)者在管理 view controller 時(shí)常會在內(nèi)存泄漏上中招般渡,以為換了新的 controller,老的 controller 就被釋放回收了芙盘,其實(shí)還沒驯用。這樣代碼一多,就會造成很多對象都沒被釋放儒老。所以用這個(gè)工具把整個(gè)應(yīng)用跑一遍蝴乔,把那些斷鏈的強(qiáng)引用清理干凈,會大有裨益驮樊。
除了上述這三類工具薇正,Instruments 還有很多實(shí)用的工具片酝,推薦大家根據(jù)自己的關(guān)注點(diǎn),花些時(shí)間去學(xué)學(xué)挖腰。比如:
Core Data:監(jiān)測讀取雕沿、緩存未命中、保存等操作猴仑,能直觀顯示是否保存次數(shù)遠(yuǎn)超實(shí)際需要审轮。
Cocoa Layout:觀察約束變化,找出布局代碼的問題所在辽俗。
Network:跟蹤 TCP / IP和 UDP / IP 連接疾渣。
Automations:創(chuàng)建和編輯測試腳本來自動化 iOS 應(yīng)用的用戶界面測試。

最后小總結(jié)下崖飘。我倒不想一味夸大 Instruments 的作用榴捡,如果應(yīng)用跑得挺痛快,沒出現(xiàn)啥調(diào)皮行為朱浴,大可把它忽略吊圾,等到問題來了再做優(yōu)化。對于新手來說翰蠢,花些時(shí)間了解 Instruments 的功能街夭,多調(diào)試多積累經(jīng)驗(yàn),這樣做出來的應(yīng)用在用戶體驗(yàn)上肯定錯(cuò)不了躏筏。
你最常用的 Instruments 工具都有哪些?歡迎與我們分享呈枉。
原文:How To Use The 3 Instruments You Should Be Using譯者:LeanCloud

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末趁尼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子猖辫,更是在濱河造成了極大的恐慌酥泞,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件啃憎,死亡現(xiàn)場離奇詭異芝囤,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)辛萍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門悯姊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人贩毕,你說我怎么就攤上這事悯许。” “怎么了辉阶?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵先壕,是天一觀的道長瘩扼。 經(jīng)常有香客問我,道長垃僚,這世上最難降的妖魔是什么集绰? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮谆棺,結(jié)果婚禮上栽燕,老公的妹妹穿的比我還像新娘。我一直安慰自己包券,他們只是感情好纫谅,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著溅固,像睡著了一般付秕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上侍郭,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天询吴,我揣著相機(jī)與錄音,去河邊找鬼亮元。 笑死猛计,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的爆捞。 我是一名探鬼主播奉瘤,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼煮甥!你這毒婦竟也來了盗温?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤成肘,失蹤者是張志新(化名)和其女友劉穎卖局,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體双霍,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡砚偶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了洒闸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片染坯。...
    茶點(diǎn)故事閱讀 39,992評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖丘逸,靈堂內(nèi)的尸體忽然破棺而出酒请,到底是詐尸還是另有隱情,我是刑警寧澤鸣个,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布羞反,位于F島的核電站布朦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏昼窗。R本人自食惡果不足惜是趴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望澄惊。 院中可真熱鬧唆途,春花似錦、人聲如沸掸驱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽毕贼。三九已至温赔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鬼癣,已是汗流浹背陶贼。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留待秃,地道東北人拜秧。 一個(gè)月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像章郁,于是被迫代替她去往敵國和親枉氮。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評論 2 355

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