CPU VS GPU
關(guān)于繪圖和動(dòng)畫(huà)有兩種處理的方式:CPU(中央處理器)和GPU(圖形處理器)。在現(xiàn)代iOS設(shè)備中割捅,都有可以運(yùn)行不同軟件的可編程芯片恩掷,但是由于歷史原因,我們可以說(shuō)CPU所做的工作都在軟件層面树灶,而GPU在硬件層面纤怒。
總的來(lái)說(shuō),我們可以用軟件(使用CPU)做任何事情天通,但是對(duì)于圖像處理泊窘,通常用硬件會(huì)更快,因?yàn)镚PU使用圖像對(duì)高度并行浮點(diǎn)運(yùn)算做了優(yōu)化像寒。由于某些原因烘豹,我們想盡可能把屏幕渲染的工作交給硬件去處理。問(wèn)題在于GPU并沒(méi)有無(wú)限制處理性能诺祸,而且一旦資源用完的話携悯,性能就會(huì)開(kāi)始下降了(即使CPU并沒(méi)有完全占用)
大多數(shù)動(dòng)畫(huà)性能優(yōu)化都是關(guān)于智能利用GPU和CPU,使得它們都不會(huì)超出負(fù)荷筷笨。于是我們首先需要知道Core Animation是如何在這兩個(gè)處理器之間分配工作的憔鬼。
動(dòng)畫(huà)的舞臺(tái)
Core Animation處在iOS的核心地位:應(yīng)用內(nèi)和應(yīng)用間都會(huì)用到它龟劲。一個(gè)簡(jiǎn)單的動(dòng)畫(huà)可能同步顯示多個(gè)app的內(nèi)容,例如當(dāng)在iPad上多個(gè)程序之間使用手勢(shì)切換逊彭,會(huì)使得多個(gè)程序同時(shí)顯示在屏幕上咸灿。在一個(gè)特定的應(yīng)用中用代碼實(shí)現(xiàn)它是沒(méi)有意義的,因?yàn)樵趇OS中不可能實(shí)現(xiàn)這種效果(App都是被沙箱管理侮叮,不能訪問(wèn)別的視圖)避矢。
動(dòng)畫(huà)和屏幕上組合的圖層實(shí)際上被一個(gè)單獨(dú)的進(jìn)程管理,而不是你的應(yīng)用程序囊榜。這個(gè)進(jìn)程就是所謂的渲染服務(wù)审胸。在iOS5和之前的版本是SpringBoard進(jìn)程(同時(shí)管理著iOS的主屏)。在iOS6之后的版本中叫做BackBoard卸勺。
當(dāng)運(yùn)行一段動(dòng)畫(huà)時(shí)候砂沛,這個(gè)過(guò)程會(huì)被四個(gè)分離的階段被打破:
布局 - 這是準(zhǔn)備你的視圖/圖層的層級(jí)關(guān)系,以及設(shè)置圖層屬性(位置曙求,背景色碍庵,邊框等等)的階段。
顯示 - 這是圖層的寄宿圖片被繪制的階段悟狱。繪制有可能涉及你的-drawRect:和-drawLayer:inContext:方法的調(diào)用路徑静浴。
準(zhǔn)備 - 這是Core Animation準(zhǔn)備發(fā)送動(dòng)畫(huà)數(shù)據(jù)到渲染服務(wù)的階段。這同時(shí)也是Core Animation將要執(zhí)行一些別的事務(wù)例如解碼動(dòng)畫(huà)過(guò)程中將要顯示的圖片的時(shí)間點(diǎn)挤渐。
提交 - 這是最后的階段苹享,Core Animation打包所有圖層和動(dòng)畫(huà)屬性,然后通過(guò)IPC(內(nèi)部處理通信)發(fā)送到渲染服務(wù)進(jìn)行顯示浴麻。
但是這些僅僅階段僅僅發(fā)生在你的應(yīng)用程序之內(nèi)得问,在動(dòng)畫(huà)在屏幕上顯示之前仍然有更多的工作。一旦打包的圖層和動(dòng)畫(huà)到達(dá)渲染服務(wù)進(jìn)程软免,他們會(huì)被反序列化來(lái)形成另一個(gè)叫做渲染樹(shù)的圖層樹(shù)(在第一章“圖層樹(shù)”中提到過(guò))宫纬。使用這個(gè)樹(shù)狀結(jié)構(gòu),渲染服務(wù)對(duì)動(dòng)畫(huà)的每一幀做出如下工作:
對(duì)所有的圖層屬性計(jì)算中間值膏萧,設(shè)置OpenGL幾何形狀(紋理化的三角形)來(lái)執(zhí)行渲染
在屏幕上渲染可見(jiàn)的三角形
所以一共有六個(gè)階段哪怔;最后兩個(gè)階段在動(dòng)畫(huà)過(guò)程中不停地重復(fù)。前五個(gè)階段都在軟件層面處理(通過(guò)CPU)向抢,只有最后一個(gè)被GPU執(zhí)行认境。而且,你真正只能控制前兩個(gè)階段:布局和顯示挟鸠。Core Animation框架在內(nèi)部處理剩下的事務(wù)叉信,你也控制不了它。
這并不是個(gè)問(wèn)題艘希,因?yàn)樵诓季趾惋@示階段硼身,你可以決定哪些由CPU執(zhí)行硅急,哪些交給GPU去做。那么改如何判斷呢佳遂?
GPU相關(guān)的操作
GPU為一個(gè)具體的任務(wù)做了優(yōu)化:它用來(lái)采集圖片和形狀(三角形)营袜,運(yùn)行變換,應(yīng)用紋理和混合然后把它們輸送到屏幕上〕笞铮現(xiàn)代iOS設(shè)備上可編程的GPU在這些操作的執(zhí)行上又很大的靈活性荚板,但是Core Animation并沒(méi)有暴露出直接的接口。除非你想繞開(kāi)Core Animation并編寫(xiě)你自己的OpenGL著色器吩屹,從根本上解決硬件加速的問(wèn)題跪另,那么剩下的所有都還是需要在CPU的軟件層面上完成。
寬泛的說(shuō)煤搜,大多數(shù)CALayer的屬性都是用GPU來(lái)繪制免绿。比如如果你設(shè)置圖層背景或者邊框的顏色,那么這些可以通過(guò)著色的三角板實(shí)時(shí)繪制出來(lái)擦盾。如果對(duì)一個(gè)contents屬性設(shè)置一張圖片嘲驾,然后裁剪它 - 它就會(huì)被紋理的三角形繪制出來(lái),而不需要軟件層面做任何繪制迹卢。
但是有一些事情會(huì)降低(基于GPU)圖層繪制辽故,比如:
太多的幾何結(jié)構(gòu) - 這發(fā)生在需要太多的三角板來(lái)做變換,以應(yīng)對(duì)處理器的柵格化的時(shí)候∩粝#現(xiàn)代iOS設(shè)備的圖形芯片可以處理幾百萬(wàn)個(gè)三角板,所以在Core Animation中幾何結(jié)構(gòu)并不是GPU的瓶頸所在蓬衡。但由于圖層在顯示之前通過(guò)IPC發(fā)送到渲染服務(wù)器的時(shí)候(圖層實(shí)際上是由很多小物體組成的特別重量級(jí)的對(duì)象)喻杈,太多的圖層就會(huì)引起CPU的瓶頸。這就限制了一次展示的圖層個(gè)數(shù)(見(jiàn)本章后續(xù)“CPU相關(guān)操作”)狰晚。
重繪 - 主要由重疊的半透明圖層引起筒饰。GPU的填充比率(用顏色填充像素的比率)是有限的,所以需要避免重繪(每一幀用相同的像素填充多次)的發(fā)生壁晒。在現(xiàn)代iOS設(shè)備上瓷们,GPU都會(huì)應(yīng)對(duì)重繪;即使是iPhone 3GS都可以處理高達(dá)2.5的重繪比率秒咐,并任然保持60幀率的渲染(這意味著你可以繪制一個(gè)半的整屏的冗余信息谬晕,而不影響性能),并且新設(shè)備可以處理更多携取。
離屏繪制 - 這發(fā)生在當(dāng)不能直接在屏幕上繪制攒钳,并且必須繪制到離屏圖片的上下文中的時(shí)候。離屏繪制發(fā)生在基于CPU或者是GPU的渲染雷滋,或者是為離屏圖片分配額外內(nèi)存不撑,以及切換繪制上下文文兢,這些都會(huì)降低GPU性能。對(duì)于特定圖層效果的使用焕檬,比如圓角姆坚,圖層遮罩,陰影或者是圖層光柵化都會(huì)強(qiáng)制Core Animation提前渲染圖層的離屏繪制实愚。但這不意味著你需要避免使用這些效果兼呵,只是要明白這會(huì)帶來(lái)性能的負(fù)面影響。
過(guò)大的圖片 - 如果視圖繪制超出GPU支持的2048x2048或者4096x4096尺寸的紋理爆侣,就必須要用CPU在圖層每次顯示之前對(duì)圖片預(yù)處理萍程,同樣也會(huì)降低性能。
CPU相關(guān)的操作
大多數(shù)工作在Core Animation的CPU都發(fā)生在動(dòng)畫(huà)開(kāi)始之前兔仰。這意味著它不會(huì)影響到幀率茫负,所以很好,但是他會(huì)延遲動(dòng)畫(huà)開(kāi)始的時(shí)間乎赴,讓你的界面看起來(lái)會(huì)比較遲鈍忍法。
以下CPU的操作都會(huì)延遲動(dòng)畫(huà)的開(kāi)始時(shí)間:
布局計(jì)算 - 如果你的視圖層級(jí)過(guò)于復(fù)雜,當(dāng)視圖呈現(xiàn)或者修改的時(shí)候榕吼,計(jì)算圖層幀率就會(huì)消耗一部分時(shí)間饿序。特別是使用iOS6的自動(dòng)布局機(jī)制尤為明顯,它應(yīng)該是比老版的自動(dòng)調(diào)整邏輯加強(qiáng)了CPU的工作羹蚣。
視圖懶加載 - iOS只會(huì)當(dāng)視圖控制器的視圖顯示到屏幕上時(shí)才會(huì)加載它原探。這對(duì)內(nèi)存使用和程序啟動(dòng)時(shí)間很有好處,但是當(dāng)呈現(xiàn)到屏幕上之前顽素,按下按鈕導(dǎo)致的許多工作都會(huì)不能被及時(shí)響應(yīng)咽弦。比如控制器從數(shù)據(jù)庫(kù)中獲取數(shù)據(jù),或者視圖從一個(gè)nib文件中加載胁出,或者涉及IO的圖片顯示(見(jiàn)后續(xù)“IO相關(guān)操作”)型型,都會(huì)比CPU正常操作慢得多。
Core Graphics繪制 - 如果對(duì)視圖實(shí)現(xiàn)了-drawRect:方法全蝶,或者CALayerDelegate的-drawLayer:inContext:方法闹蒜,那么在繪制任何東西之前都會(huì)產(chǎn)生一個(gè)巨大的性能開(kāi)銷。為了支持對(duì)圖層內(nèi)容的任意繪制抑淫,Core Animation必須創(chuàng)建一個(gè)內(nèi)存中等大小的寄宿圖片绷落。然后一旦繪制結(jié)束之后,必須把圖片數(shù)據(jù)通過(guò)IPC傳到渲染服務(wù)器始苇。在此基礎(chǔ)上嘱函,Core Graphics繪制就會(huì)變得十分緩慢,所以在一個(gè)對(duì)性能十分挑剔的場(chǎng)景下這樣做十分不好埂蕊。
解壓圖片 - PNG或者JPEG壓縮之后的圖片文件會(huì)比同質(zhì)量的位圖小得多往弓。但是在圖片繪制到屏幕上之前疏唾,必須把它擴(kuò)展成完整的未解壓的尺寸(通常等同于圖片寬 x 長(zhǎng) x 4個(gè)字節(jié))。為了節(jié)省內(nèi)存函似,iOS通常直到真正繪制的時(shí)候才去解碼圖片(14章“圖片IO”會(huì)更詳細(xì)討論)槐脏。根據(jù)你加載圖片的方式,第一次對(duì)圖層內(nèi)容賦值的時(shí)候(直接或者間接使用UIImageView)或者把它繪制到Core Graphics中撇寞,都需要對(duì)它解壓顿天,這樣的話,對(duì)于一個(gè)較大的圖片蔑担,都會(huì)占用一定的時(shí)間牌废。
當(dāng)圖層被成功打包,發(fā)送到渲染服務(wù)器之后啤握,CPU仍然要做如下工作:為了顯示屏幕上的圖層鸟缕,Core Animation必須對(duì)渲染樹(shù)種的每個(gè)可見(jiàn)圖層通過(guò)OpenGL循環(huán)轉(zhuǎn)換成紋理三角板。由于GPU并不知曉Core Animation圖層的任何結(jié)構(gòu)排抬,所以必須要由CPU做這些事情懂从。這里CPU涉及的工作和圖層個(gè)數(shù)成正比,所以如果在你的層級(jí)關(guān)系中有太多的圖層蹲蒲,就會(huì)導(dǎo)致CPU沒(méi)一幀的渲染番甩,即使這些事情不是你的應(yīng)用程序可控的。
IO相關(guān)操作
還有一項(xiàng)沒(méi)涉及的就是IO相關(guān)工作届搁。上下文中的IO(輸入/輸出)指的是例如閃存或者網(wǎng)絡(luò)接口的硬件訪問(wèn)缘薛。一些動(dòng)畫(huà)可能需要從山村(甚至是遠(yuǎn)程URL)來(lái)加載。一個(gè)典型的例子就是兩個(gè)視圖控制器之間的過(guò)渡效果卡睦,這就需要從一個(gè)nib文件或者是它的內(nèi)容中懶加載宴胧,或者一個(gè)旋轉(zhuǎn)的圖片,可能在內(nèi)存中尺寸太大么翰,需要?jiǎng)討B(tài)滾動(dòng)來(lái)加載牺汤。
IO比內(nèi)存訪問(wèn)更慢辽旋,所以如果動(dòng)畫(huà)涉及到IO浩嫌,就是一個(gè)大問(wèn)題〔古撸總的來(lái)說(shuō)码耐,這就需要使用聰敏但尷尬的技術(shù),也就是多線程溶其,緩存和投機(jī)加載(提前加載當(dāng)前不需要的資源骚腥,但是之后可能需要用到)。這些技術(shù)將會(huì)在第14章中討論瓶逃。
測(cè)量束铭,而不是猜測(cè)
于是現(xiàn)在你知道有哪些點(diǎn)可能會(huì)影響動(dòng)畫(huà)性能廓块,那該如何修復(fù)呢?好吧契沫,其實(shí)不需要带猴。有很多種詭計(jì)來(lái)優(yōu)化動(dòng)畫(huà),但如果盲目使用的話懈万,可能會(huì)造成更多性能上的問(wèn)題拴清,而不是修復(fù)。
如何正確的測(cè)量而不是猜測(cè)這點(diǎn)很重要会通。根據(jù)性能相關(guān)的知識(shí)寫(xiě)出代碼不同于倉(cāng)促的優(yōu)化口予。前者很好,后者實(shí)際上就是在浪費(fèi)時(shí)間涕侈。
那該如何測(cè)量呢沪停?第一步就是確保在真實(shí)環(huán)境下測(cè)試你的程序。
真機(jī)測(cè)試驾凶,而不是模擬器
當(dāng)你開(kāi)始做一些性能方面的工作時(shí)牙甫,一定要在真機(jī)上測(cè)試,而不是模擬器调违。模擬器雖然是加快開(kāi)發(fā)效率的一把利器窟哺,但它不能提供準(zhǔn)確的真機(jī)性能參數(shù)。
模擬器運(yùn)行在你的Mac上技肩,然而Mac上的CPU往往比iOS設(shè)備要快且轨。相反,Mac上的GPU和iOS設(shè)備的完全不一樣虚婿,模擬器不得已要在軟件層面(CPU)模擬設(shè)備的GPU旋奢,這意味著GPU相關(guān)的操作在模擬器上運(yùn)行的更慢,尤其是使用CAEAGLLayer來(lái)寫(xiě)一些OpenGL的代碼時(shí)候然痊。
這就是說(shuō)在模擬器上的測(cè)試出的性能會(huì)高度失真至朗。如果動(dòng)畫(huà)在模擬器上運(yùn)行流暢,可能在真機(jī)上十分糟糕剧浸。如果在模擬器上運(yùn)行的很卡锹引,也可能在真機(jī)上很平滑。你無(wú)法確定唆香。
另一件重要的事情就是性能測(cè)試一定要用發(fā)布配置嫌变,而不是調(diào)試模式。因?yàn)楫?dāng)用發(fā)布環(huán)境打包的時(shí)候躬它,編譯器會(huì)引入一系列提高性能的優(yōu)化腾啥,例如去掉調(diào)試符號(hào)或者移除并重新組織代碼。你也可以自己做到這些,例如在發(fā)布環(huán)境禁用NSLog語(yǔ)句倘待。你只關(guān)心發(fā)布性能疮跑,那才是你需要測(cè)試的點(diǎn)。
最后凸舵,最好在你支持的設(shè)備中性能最差的設(shè)備上測(cè)試:如果基于iOS6開(kāi)發(fā)祸挪,這意味著最好在iPhone 3GS或者iPad2上測(cè)試。如果可能的話贞间,測(cè)試不同的設(shè)備和iOS版本贿条,因?yàn)樘O(píng)果在不同的iOS版本和設(shè)備中做了一些改變,這也可能影響到一些性能增热。例如iPad3明顯要在動(dòng)畫(huà)渲染上比iPad2慢很多整以,因?yàn)殇秩?倍多的像素點(diǎn)(為了支持視網(wǎng)膜顯示)。
保持一致的幀率
為了做到動(dòng)畫(huà)的平滑峻仇,你需要以60FPS(幀每秒)的速度運(yùn)行公黑,以同步屏幕刷新速率。通過(guò)基于NSTimer或者CADisplayLink的動(dòng)畫(huà)你可以降低到30FPS摄咆,而且效果還不錯(cuò)凡蚜,但是沒(méi)辦法通過(guò)Core Animation做到這點(diǎn)。如果不保持60FPS的速率吭从,就可能隨機(jī)丟幀朝蜘,影響到體驗(yàn)。
你可以在使用的過(guò)程中明顯感到有沒(méi)有丟幀涩金,但沒(méi)辦法通過(guò)肉眼來(lái)得到具體的數(shù)據(jù)谱醇,也沒(méi)法知道你的做法有沒(méi)有真的提高性能。你需要的是一系列精確的數(shù)據(jù)步做。
你可以在程序中用CADisplayLink來(lái)測(cè)量幀率(就像11章“基于定時(shí)器的動(dòng)畫(huà)”中那樣)副渴,然后在屏幕上顯示出來(lái),但應(yīng)用內(nèi)的FPS顯示并不能夠完全真實(shí)測(cè)量出Core Animation性能全度,因?yàn)樗鼉H僅測(cè)出應(yīng)用內(nèi)的幀率煮剧。我們知道很多動(dòng)畫(huà)都在應(yīng)用之外發(fā)生(在渲染服務(wù)器進(jìn)程中處理),但同時(shí)應(yīng)用內(nèi)FPS計(jì)數(shù)的確可以對(duì)某些性能問(wèn)題提供參考将鸵,一旦找出一個(gè)問(wèn)題的地方勉盅,你就需要得到更多精確詳細(xì)的數(shù)據(jù)來(lái)定位到問(wèn)題所在。蘋(píng)果提供了一個(gè)強(qiáng)大的Instruments工具集來(lái)幫我們做到這些咨堤。
Instruments
Instruments是Xcode套件中沒(méi)有被充分利用的一個(gè)工具菇篡。很多iOS開(kāi)發(fā)者從沒(méi)用過(guò)Instruments漩符,或者只是用Leaks工具檢測(cè)循環(huán)引用一喘。實(shí)際上有很多Instruments工具,包括為動(dòng)畫(huà)性能調(diào)優(yōu)的東西。
你可以通過(guò)在菜單中選擇Profile選項(xiàng)來(lái)打開(kāi)Instruments(在這之前凸克,記住要把目標(biāo)設(shè)置成iOS設(shè)備议蟆,而不是模擬器)。然后將會(huì)顯示出圖12.1(如果沒(méi)有看到所有選項(xiàng)萎战,你可能設(shè)置成了模擬器選項(xiàng))咐容。
圖12.1 Instruments工具選項(xiàng)窗口
就像之前提到的那樣,你應(yīng)該始終將程序設(shè)置成發(fā)布選項(xiàng)蚂维。幸運(yùn)的是戳粒,配置文件默認(rèn)就是發(fā)布選項(xiàng),所以你不需要在分析的時(shí)候調(diào)整編譯策略虫啥。
我們將討論如下幾個(gè)工具:
時(shí)間分析器 - 用來(lái)測(cè)量被方法/函數(shù)打斷的CPU使用情況蔚约。
Core Animation - 用來(lái)調(diào)試各種Core Animation性能問(wèn)題。
OpenGL ES驅(qū)動(dòng) - 用來(lái)調(diào)試GPU性能問(wèn)題涂籽。這個(gè)工具在編寫(xiě)Open GL代碼的時(shí)候很有用苹祟,但有時(shí)也用來(lái)處理Core Animation的工作。
Instruments的一個(gè)很棒的功能在于它可以創(chuàng)建我們自定義的工具集评雌。除了你初始選擇的工具之外树枫,如果在Instruments中打開(kāi)Library窗口,你可以拖拽別的工具到左側(cè)邊欄景东。我們將創(chuàng)建以上我們提到的三個(gè)工具砂轻,然后就可以并行使用了(見(jiàn)圖12.2)。
圖12.2 添加額外的工具到Instruments側(cè)邊欄
時(shí)間分析器
時(shí)間分析器工具用來(lái)檢測(cè)CPU的使用情況斤吐。它可以告訴我們程序中的哪個(gè)方法正在消耗大量的CPU時(shí)間舔清。使用大量的CPU并不一定是個(gè)問(wèn)題 - 你可能期望動(dòng)畫(huà)路徑對(duì)CPU非常依賴,因?yàn)閯?dòng)畫(huà)往往是iOS設(shè)備中最苛刻的任務(wù)曲初。
但是如果你有性能問(wèn)題体谒,查看CPU時(shí)間對(duì)于判斷性能是不是和CPU相關(guān),以及定位到函數(shù)都很有幫助(見(jiàn)圖12.3)臼婆。
圖12.3 時(shí)間分析器工具
時(shí)間分析器有一些選項(xiàng)來(lái)幫助我們定位到我們關(guān)心的的方法抒痒。可以使用左側(cè)的復(fù)選框來(lái)打開(kāi)颁褂。其中最有用的是如下幾點(diǎn):
通過(guò)線程分離 - 這可以通過(guò)執(zhí)行的線程進(jìn)行分組故响。如果代碼被多線程分離的話,那么就可以判斷到底是哪個(gè)線程造成了問(wèn)題颁独。
隱藏系統(tǒng)庫(kù) - 可以隱藏所有蘋(píng)果的框架代碼彩届,來(lái)幫助我們尋找哪一段代碼造成了性能瓶頸。由于我們不能優(yōu)化框架方法誓酒,所以這對(duì)定位到我們能實(shí)際修復(fù)的代碼很有用樟蠕。
只顯示Obj-C代碼 - 隱藏除了Objective-C之外的所有代碼贮聂。大多數(shù)內(nèi)部的Core Animation代碼都是用C或者C++函數(shù),所以這對(duì)我們集中精力到我們代碼中顯式調(diào)用的方法就很有用寨辩。
Core Animation
Core Animation工具用來(lái)監(jiān)測(cè)Core Animation性能吓懈。它給我們提供了周期性的FPS,并且考慮到了發(fā)生在程序之外的動(dòng)畫(huà)(見(jiàn)圖12.4)靡狞。
圖12.4 使用可視化調(diào)試選項(xiàng)的Core Animation工具
Core Animation工具也提供了一系列復(fù)選框選項(xiàng)來(lái)幫助調(diào)試渲染瓶頸:
- Color Blended Layers - 這個(gè)選項(xiàng)基于渲染程度對(duì)屏幕中的混合區(qū)域進(jìn)行綠到紅的高亮(也就是多個(gè)半透明圖層的疊加)耻警。由于重繪的原因,混合對(duì)GPU性能會(huì)有影響甸怕,同時(shí)也是滑動(dòng)或者動(dòng)畫(huà)幀率下降的罪魁禍?zhǔn)字弧?/li>
- ColorHitsGreenandMissesRed - 當(dāng)使用shouldRasterizep屬性的時(shí)候甘穿,耗時(shí)的圖層繪制會(huì)被緩存,然后當(dāng)做一個(gè)簡(jiǎn)單的扁平圖片呈現(xiàn)梢杭。當(dāng)緩存再生的時(shí)候這個(gè)選項(xiàng)就用紅色對(duì)柵格化圖層進(jìn)行了高亮扒磁。如果緩存頻繁再生的話,就意味著柵格化可能會(huì)有負(fù)面的性能影響了(更多關(guān)于使用shouldRasterize的細(xì)節(jié)見(jiàn)第15章“圖層性能”)式曲。
- Color Copied Images - 有時(shí)候寄宿圖片的生成意味著Core Animation被強(qiáng)制生成一些圖片妨托,然后發(fā)送到渲染服務(wù)器,而不是簡(jiǎn)單的指向原始指針吝羞。這個(gè)選項(xiàng)把這些圖片渲染成藍(lán)色兰伤。復(fù)制圖片對(duì)內(nèi)存和CPU使用來(lái)說(shuō)都是一項(xiàng)非常昂貴的操作,所以應(yīng)該盡可能的避免钧排。
- Color Immediately - 通常Core Animation Instruments以每毫秒10次的頻率更新圖層調(diào)試顏色敦腔。對(duì)某些效果來(lái)說(shuō),這顯然太慢了恨溜。這個(gè)選項(xiàng)就可以用來(lái)設(shè)置每幀都更新(可能會(huì)影響到渲染性能符衔,而且會(huì)導(dǎo)致幀率測(cè)量不準(zhǔn),所以不要一直都設(shè)置它)糟袁。
- Color Misaligned Images - 這里會(huì)高亮那些被縮放或者拉伸以及沒(méi)有正確對(duì)齊到像素邊界的圖片(也就是非整型坐標(biāo))判族。這些中的大多數(shù)通常都會(huì)導(dǎo)致圖片的不正常縮放项戴,如果把一張大圖當(dāng)縮略圖顯示形帮,或者不正確地模糊圖像,那么這個(gè)選項(xiàng)將會(huì)幫你識(shí)別出問(wèn)題所在周叮。
- Color Offscreen-Rendered Yellow - 這里會(huì)把那些需要離屏渲染的圖層高亮成黃色辩撑。這些圖層很可能需要用shadowPath或者shouldRasterize來(lái)優(yōu)化。
- Color OpenGL Fast Path Blue - 這個(gè)選項(xiàng)會(huì)對(duì)任何直接使用OpenGL繪制的圖層進(jìn)行高亮仿耽。如果僅僅使用UIKit或者Core Animation的API合冀,那么不會(huì)有任何效果。如果使用GLKView或者CAEAGLLayer项贺,那如果不顯示藍(lán)色塊的話就意味著你正在強(qiáng)制CPU渲染額外的紋理君躺,而不是繪制到屏幕峭判。
- Flash Updated Regions - 這個(gè)選項(xiàng)會(huì)對(duì)重繪的內(nèi)容高亮成黃色(也就是任何在軟件層面使用Core Graphics繪制的圖層)。這種繪圖的速度很慢晰洒。如果頻繁發(fā)生這種情況的話,這意味著有一個(gè)隱藏的bug或者說(shuō)通過(guò)增加緩存或者使用替代方案會(huì)有提升性能的空間啥箭。
這些高亮圖層的選項(xiàng)同樣在iOS模擬器的調(diào)試菜單也可用(圖12.5)谍珊。我們之前說(shuō)過(guò)用模擬器測(cè)試性能并不好,但如果你能通過(guò)這些高亮選項(xiàng)識(shí)別出性能問(wèn)題出在什么地方的話急侥,那么使用iOS模擬器來(lái)驗(yàn)證問(wèn)題是否解決也是比真機(jī)測(cè)試更有效的砌滞。
圖12.5 iOS模擬器中Core Animation可視化調(diào)試選項(xiàng)
OpenGL ES驅(qū)動(dòng)
OpenGL ES驅(qū)動(dòng)工具可以幫你測(cè)量GPU的利用率,同樣也是一個(gè)很好的來(lái)判斷和GPU相關(guān)動(dòng)畫(huà)性能的指示器坏怪。它同樣也提供了類似Core Animation那樣顯示FPS的工具(圖12.6)。
圖12.6 OpenGL ES驅(qū)動(dòng)工具
側(cè)欄的郵編是一系列有用的工具。其中和Core Animation性能最相關(guān)的是如下幾點(diǎn):
Renderer Utilization - 如果這個(gè)值超過(guò)了~50%护戳,就意味著你的動(dòng)畫(huà)可能對(duì)幀率有所限制看成,很可能因?yàn)殡x屏渲染或者是重繪導(dǎo)致的過(guò)度混合。
Tiler Utilization - 如果這個(gè)值超過(guò)了~50%鹏秋,就意味著你的動(dòng)畫(huà)可能限制于幾何結(jié)構(gòu)方面尊蚁,也就是在屏幕上有太多的圖層占用了。
一個(gè)可用的案例
現(xiàn)在我們已經(jīng)對(duì)Instruments中動(dòng)畫(huà)性能工具非常熟悉了侣夷,那么可以用它在現(xiàn)實(shí)中解決一些實(shí)際問(wèn)題横朋。
我們創(chuàng)建一個(gè)簡(jiǎn)單的顯示模擬聯(lián)系人姓名和頭像列表的應(yīng)用。注意即使把頭像圖片存在應(yīng)用本地百拓,為了使應(yīng)用看起來(lái)更真實(shí)琴锭,我們分別實(shí)時(shí)加載圖片,而不是用–imageNamed:預(yù)加載衙传。同樣添加一些圖層陰影來(lái)使得列表顯示得更真實(shí)决帖。清單12.1展示了最初版本的實(shí)現(xiàn)。
清單12.1 使用假數(shù)據(jù)的一個(gè)簡(jiǎn)單聯(lián)系人列表
#import "ViewController.h"
#import
@interface ViewController ()
@property (nonatomic, strong) NSArray *items;
@property (nonatomic, weak) IBOutlet UITableView *tableView;
@end
@implementation ViewController
- (NSString *)randomName
{
NSArray *first = @[@"Alice", @"Bob", @"Bill", @"Charles", @"Dan", @"Dave", @"Ethan", @"Frank"];
NSArray *last = @[@"Appleseed", @"Bandicoot", @"Caravan", @"Dabble", @"Ernest", @"Fortune"];
NSUInteger index1 = (rand()/(double)INT_MAX) * [first count];
NSUInteger index2 = (rand()/(double)INT_MAX) * [last count];
return [NSString stringWithFormat:@"%@ %@", first[index1], last[index2]];
}
- (NSString *)randomAvatar
{
NSArray *images = @[@"Snowman", @"Igloo", @"Cone", @"Spaceship", @"Anchor", @"Key"];
NSUInteger index = (rand()/(double)INT_MAX) * [images count];
return images[index];
}
- (void)viewDidLoad
{
[super viewDidLoad];
//set up data
NSMutableArray *array = [NSMutableArray array];
for (int i = 0; i < 1000; i++) {
//add name
[array addObject:@{@"name": [self randomName], @"image": [self randomAvatar]}];
}
self.items = array;
//register cell class
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"Cell"];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.items count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//dequeue cell
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
//load image
NSDictionary *item = self.items[indexPath.row];
NSString *filePath = [[NSBundle mainBundle] pathForResource:item[@"image"] ofType:@"png"];
//set image and text
cell.imageView.image = [UIImage imageWithContentsOfFile:filePath];
cell.textLabel.text = item[@"name"];
//set image shadow
cell.imageView.layer.shadowOffset = CGSizeMake(0, 5);
cell.imageView.layer.shadowOpacity = 0.75;
cell.clipsToBounds = YES;
//set text shadow
cell.textLabel.backgroundColor = [UIColor clearColor];
cell.textLabel.layer.shadowOffset = CGSizeMake(0, 2);
cell.textLabel.layer.shadowOpacity = 0.5;
return cell;
}
@end
當(dāng)快速滑動(dòng)的時(shí)候就會(huì)非潮痛罚卡(見(jiàn)圖12.7的FPS計(jì)數(shù)器)古瓤。
圖12.7 滑動(dòng)幀率降到15FPS
僅憑直覺(jué),我們猜測(cè)性能瓶頸應(yīng)該在圖片加載腺阳。我們實(shí)時(shí)從閃存加載圖片落君,而且沒(méi)有緩存,所以很可能是這個(gè)原因亭引。我們可以用一些很贊的代碼修復(fù)绎速,然后使用GCD異步加載圖片,然后緩存焙蚓。纹冤。洒宝。等一下,在開(kāi)始編碼之前萌京,測(cè)試一下假設(shè)是否成立雁歌。首先用我們的三個(gè)Instruments工具分析一下程序來(lái)定位問(wèn)題。我們推測(cè)問(wèn)題可能和圖片加載相關(guān)知残,所以用Time Profiler工具來(lái)試試(圖12.8)靠瞎。
圖12.8 用The timing profile分析聯(lián)系人列表
-tableView:cellForRowAtIndexPath:中的CPU時(shí)間總利用率只有~28%(也就是加載頭像圖片的地方),非常低求妹。于是建議是CPU/IO并不是真正的限制因素乏盐。然后看看是不是GPU的問(wèn)題:在OpenGL ES Driver工具中檢測(cè)GPU利用率(圖12.9)。
圖12.9 OpenGL ES Driver工具顯示的GPU利用率
渲染服務(wù)利用率的值達(dá)到51%和63%制恍「改埽看起來(lái)GPU需要做很多工作來(lái)渲染聯(lián)系人列表。
為什么GPU利用率這么高呢净神?我們來(lái)用Core Animation調(diào)試工具選項(xiàng)來(lái)檢查屏幕何吝。首先打開(kāi)Color Blended Layers(圖12.10)。
圖12.10 使用Color Blended Layers選項(xiàng)調(diào)試程序
屏幕中所有紅色的部分都意味著字符標(biāo)簽視圖的高級(jí)別混合鹃唯,這很正常岔霸,因?yàn)槲覀儼驯尘霸O(shè)置成了透明色來(lái)顯示陰影效果。這就解釋了為什么渲染利用率這么高了俯渤。
那么離屏繪制呢呆细?打開(kāi)Core Animation工具的Color Offscreen - Rendered Yellow選項(xiàng)(圖12.11)。
圖12.11 Color Offscreen–Rendered Yellow選項(xiàng)
所有的表格單元內(nèi)容都在離屏繪制八匠。這一定是因?yàn)槲覀兘o圖片和標(biāo)簽視圖添加的陰影效果絮爷。在代碼中禁用陰影,然后看下性能是否有提高(圖12.12)梨树。
圖12.12 禁用陰影之后運(yùn)行程序接近60FPS
問(wèn)題解決了坑夯。干掉陰影之后,滑動(dòng)很流暢抡四。但是我們的聯(lián)系人列表看起來(lái)沒(méi)有之前好了柜蜈。那如何保持陰影效果而且不會(huì)影響性能呢?
好吧指巡,每一行的字符和頭像在每一幀刷新的時(shí)候并不需要變淑履,所以看起來(lái)UITableViewCell的圖層非常適合做緩存。我們可以使用shouldRasterize來(lái)緩存圖層內(nèi)容藻雪。這將會(huì)讓圖層離屏之后渲染一次然后把結(jié)果保存起來(lái)秘噪,直到下次利用的時(shí)候去更新(見(jiàn)清單12.2)。
清單12.2 使用shouldRasterize提高性能
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//dequeue cell
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"Cell"
forIndexPath:indexPath];
...
//set text shadow
cell.textLabel.backgroundColor = [UIColor clearColor];
cell.textLabel.layer.shadowOffset = CGSizeMake(0, 2);
cell.textLabel.layer.shadowOpacity = 0.5;
//rasterize
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
return cell;
}
我們?nèi)匀浑x屏繪制圖層內(nèi)容勉耀,但是由于顯式地禁用了柵格化指煎,Core Animation就對(duì)繪圖緩存了結(jié)果蹋偏,于是對(duì)提高了性能。我們可以驗(yàn)證緩存是否有效至壤,在Core Animation工具中點(diǎn)擊Color Hits Green and Misses Red選項(xiàng)(圖12.13)威始。
圖12.13 Color Hits Green and Misses Red驗(yàn)證了緩存有效
結(jié)果和預(yù)期一致 - 大部分都是綠色,只有當(dāng)滑動(dòng)到屏幕上的時(shí)候會(huì)閃爍成紅色像街。因此黎棠,現(xiàn)在幀率更加平滑了。
所以我們最初的設(shè)想是錯(cuò)的宅广。圖片的加載并不是真正的瓶頸所在葫掉,而且試圖把它置于一個(gè)復(fù)雜的多線程加載和緩存的實(shí)現(xiàn)都將是徒勞些举。所以在動(dòng)手修復(fù)之前驗(yàn)證問(wèn)題所在是個(gè)很好的習(xí)慣跟狱!
總結(jié)
在這章中,我們學(xué)習(xí)了Core Animation是如何渲染户魏,以及我們可能出現(xiàn)的瓶頸所在驶臊。你同樣學(xué)習(xí)了如何使用Instruments來(lái)檢測(cè)和修復(fù)性能問(wèn)題。
在下三章中叼丑,我們將對(duì)每個(gè)普通程序的性能陷阱進(jìn)行詳細(xì)討論关翎,然后學(xué)習(xí)如何修復(fù)。