[iOS Animation]-CALayer 性能優化一

性能優化

代碼應該運行的儘可能快,而不是更快 - 理查德git

在第一和第二部分,咱們瞭解了Core Animation提供的關於繪製和動畫的一些特性。Core Animation功能和性能都很是強大,但若是你對背後的原理不清楚的話也會下降效率。讓它達到最優的狀態是一門藝術。在這章中,咱們將探究一些動畫運行慢的緣由,以及如何去修復這些問題。github

CPU VS GPU

關於繪圖和動畫有兩種處理的方式:CPU(中央處理器)和GPU(圖形處理器)。在現代iOS設備中,都有能夠運行不一樣軟件的可編程芯片,可是因爲歷史緣由,咱們能夠說CPU所作的工做都在軟件層面,而GPU在硬件層面。數據庫

總的來講,咱們能夠用軟件(使用CPU)作任何事情,可是對於圖像處理,一般用硬件會更快,由於GPU使用圖像對高度並行浮點運算作了優化。因爲某些緣由,咱們想盡量把屏幕渲染的工做交給硬件去處理。問題在於GPU並無無限制處理性能,並且一旦資源用完的話,性能就會開始降低了(即便CPU並無徹底佔用)編程

大多數動畫性能優化都是關於智能利用GPU和CPU,使得它們都不會超出負荷。因而咱們首先須要知道Core Animation是如何在這兩個處理器之間分配工做的。緩存

動畫的舞臺

Core Animation處在iOS的核心地位:應用內和應用間都會用到它。一個簡單的動畫可能同步顯示多個app的內容,例如當在iPad上多個程序之間使用手勢切換,會使得多個程序同時顯示在屏幕上。在一個特定的應用中用代碼實現它是沒有意義的,由於在iOS中不可能實現這種效果(App都是被沙箱管理,不能訪問別的視圖)。性能優化

動畫和屏幕上組合的圖層實際上被一個單獨的進程管理,而不是你的應用程序。這個進程就是所謂的渲染服務。在iOS5和以前的版本是SpringBoard進程(同時管理着iOS的主屏)。在iOS6以後的版本中叫作BackBoard服務器

當運行一段動畫時候,這個過程會被四個分離的階段被打破:網絡

  • 佈局 - 這是準備你的視圖/圖層的層級關係,以及設置圖層屬性(位置,背景色,邊框等等)的階段。多線程

  • 顯示 - 這是圖層的寄宿圖片被繪製的階段。繪製有可能涉及你的-drawRect:-drawLayer:inContext:方法的調用路徑。app

  • 準備 - 這是Core Animation準備發送動畫數據到渲染服務的階段。這同時也是Core Animation將要執行一些別的事務例如解碼動畫過程當中將要顯示的圖片的時間點。

  • 提交 - 這是最後的階段,Core Animation打包全部圖層和動畫屬性,而後經過IPC(內部處理通訊)發送到渲染服務進行顯示。

可是這些僅僅階段僅僅發生在你的應用程序以內,在動畫在屏幕上顯示以前仍然有更多的工做。一旦打包的圖層和動畫到達渲染服務進程,他們會被反序列化來造成另外一個叫作渲染樹的圖層樹(在第一章「圖層樹」中提到過)。使用這個樹狀結構,渲染服務對動畫的每一幀作出以下工做:

  • 對全部的圖層屬性計算中間值,設置OpenGL幾何形狀(紋理化的三角形)來執行渲染

  • 在屏幕上渲染可見的三角形

因此一共有六個階段;最後兩個階段在動畫過程當中不停地重複。前五個階段都在軟件層面處理(經過CPU),只有最後一個被GPU執行。並且,你真正只能控制前兩個階段:佈局和顯示。Core Animation框架在內部處理剩下的事務,你也控制不了它。

這並非個問題,由於在佈局和顯示階段,你能夠決定哪些由CPU執行,哪些交給GPU去作。那麼改如何判斷呢?

GPU相關的操做

GPU爲一個具體的任務作了優化:它用來採集圖片和形狀(三角形),運行變換,應用紋理和混合而後把它們輸送到屏幕上。現代iOS設備上可編程的GPU在這些操做的執行上又很大的靈活性,可是Core Animation並無暴露出直接的接口。除非你想繞開Core Animation並編寫你本身的OpenGL着色器,從根本上解決硬件加速的問題,那麼剩下的全部都仍是須要在CPU的軟件層面上完成。

寬泛的說,大多數CALayer的屬性都是用GPU來繪製。好比若是你設置圖層背景或者邊框的顏色,那麼這些能夠經過着色的三角板實時繪製出來。若是對一個contents屬性設置一張圖片,而後裁剪它 - 它就會被紋理的三角形繪製出來,而不須要軟件層面作任何繪製。

可是有一些事情會下降(基於GPU)圖層繪製,好比:

  • 太多的幾何結構 - 這發生在須要太多的三角板來作變換,以應對處理器的柵格化的時候。現代iOS設備的圖形芯片能夠處理幾百萬個三角板,因此在Core Animation中幾何結構並非GPU的瓶頸所在。但因爲圖層在顯示以前經過IPC發送到渲染服務器的時候(圖層其實是由不少小物體組成的特別重量級的對象),太多的圖層就會引發CPU的瓶頸。這就限制了一次展現的圖層個數(見本章後續「CPU相關操做」)。

  • 重繪 - 主要由重疊的半透明圖層引發。GPU的填充比率(用顏色填充像素的比率)是有限的,因此須要避免重繪(每一幀用相同的像素填充屢次)的發生。在現代iOS設備上,GPU都會應對重繪;即便是iPhone 3GS均可以處理高達2.5的重繪比率,並任然保持60幀率的渲染(這意味着你能夠繪製一個半的整屏的冗餘信息,而不影響性能),而且新設備能夠處理更多。

  • 離屏繪製 - 這發生在當不能直接在屏幕上繪製,而且必須繪製到離屏圖片的上下文中的時候。離屏繪製發生在基於CPU或者是GPU的渲染,或者是爲離屏圖片分配額外內存,以及切換繪製上下文,這些都會下降GPU性能。對於特定圖層效果的使用,好比圓角,圖層遮罩,陰影或者是圖層光柵化都會強制Core Animation提早渲染圖層的離屏繪製。但這不意味着你須要避免使用這些效果,只是要明白這會帶來性能的負面影響。

  • 過大的圖片 - 若是視圖繪製超出GPU支持的2048x2048或者4096x4096尺寸的紋理,就必需要用CPU在圖層每次顯示以前對圖片預處理,一樣也會下降性能。

CPU相關的操做

大多數工做在Core Animation的CPU都發生在動畫開始以前。這意味着它不會影響到幀率,因此很好,可是他會延遲動畫開始的時間,讓你的界面看起來會比較遲鈍。

如下CPU的操做都會延遲動畫的開始時間:

  • 佈局計算 - 若是你的視圖層級過於複雜,當視圖呈現或者修改的時候,計算圖層幀率就會消耗一部分時間。特別是使用iOS6的自動佈局機制尤其明顯,它應該是比老版的自動調整邏輯增強了CPU的工做。

  • 視圖懶加載 - iOS只會當視圖控制器的視圖顯示到屏幕上時纔會加載它。這對內存使用和程序啓動時間頗有好處,可是當呈現到屏幕上以前,按下按鈕致使的許多工做都會不能被及時響應。好比控制器從數據庫中獲取數據,或者視圖從一個nib文件中加載,或者涉及IO的圖片顯示(見後續「IO相關操做」),都會比CPU正常操做慢得多。

  • Core Graphics繪製 - 若是對視圖實現了-drawRect:方法,或者CALayerDelegate-drawLayer:inContext:方法,那麼在繪製任何東西以前都會產生一個巨大的性能開銷。爲了支持對圖層內容的任意繪製,Core Animation必須建立一個內存中等大小的寄宿圖片。而後一旦繪製結束以後,必須把圖片數據經過IPC傳到渲染服務器。在此基礎上,Core Graphics繪製就會變得十分緩慢,因此在一個對性能十分挑剔的場景下這樣作十分很差。

  • 解壓圖片 - PNG或者JPEG壓縮以後的圖片文件會比同質量的位圖小得多。可是在圖片繪製到屏幕上以前,必須把它擴展成完整的未解壓的尺寸(一般等同於圖片寬 x 長 x 4個字節)。爲了節省內存,iOS一般直到真正繪製的時候纔去解碼圖片(14章「圖片IO」會更詳細討論)。根據你加載圖片的方式,第一次對圖層內容賦值的時候(直接或者間接使用UIImageView)或者把它繪製到Core Graphics中,都須要對它解壓,這樣的話,對於一個較大的圖片,都會佔用必定的時間。

當圖層被成功打包,發送到渲染服務器以後,CPU仍然要作以下工做:爲了顯示屏幕上的圖層,Core Animation必須對渲染樹種的每一個可見圖層經過OpenGL循環轉換成紋理三角板。因爲GPU並不知曉Core Animation圖層的任何結構,因此必需要由CPU作這些事情。這裏CPU涉及的工做和圖層個數成正比,因此若是在你的層級關係中有太多的圖層,就會致使CPU沒一幀的渲染,即便這些事情不是你的應用程序可控的。

IO相關操做

還有一項沒涉及的就是IO相關工做。上下文中的IO(輸入/輸出)指的是例如閃存或者網絡接口的硬件訪問。一些動畫可能須要從山村(甚至是遠程URL)來加載。一個典型的例子就是兩個視圖控制器之間的過渡效果,這就須要從一個nib文件或者是它的內容中懶加載,或者一個旋轉的圖片,可能在內存中尺寸太大,須要動態滾動來加載。

IO比內存訪問更慢,因此若是動畫涉及到IO,就是一個大問題。總的來講,這就須要使用聰敏但尷尬的技術,也就是多線程,緩存和投機加載(提早加載當前不須要的資源,可是以後可能須要用到)。這些技術將會在第14章中討論。

測量,而不是猜想

因而如今你知道有哪些點可能會影響動畫性能,那該如何修復呢?好吧,其實不須要。有不少種詭計來優化動畫,但若是盲目使用的話,可能會形成更多性能上的問題,而不是修復。

如何正確的測量而不是猜想這點很重要。根據性能相關的知識寫出代碼不一樣於倉促的優化。前者很好,後者實際上就是在浪費時間。

那該如何測量呢?第一步就是確保在真實環境下測試你的程序。

真機測試,而不是模擬器

當你開始作一些性能方面的工做時,必定要在真機上測試,而不是模擬器。模擬器雖然是加快開發效率的一把利器,但它不能提供準確的真機性能參數。

模擬器運行在你的Mac上,然而Mac上的CPU每每比iOS設備要快。相反,Mac上的GPU和iOS設備的徹底不同,模擬器不得已要在軟件層面(CPU)模擬設備的GPU,這意味着GPU相關的操做在模擬器上運行的更慢,尤爲是使用CAEAGLLayer來寫一些OpenGL的代碼時候。

這就是說在模擬器上的測試出的性能會高度失真。若是動畫在模擬器上運行流暢,可能在真機上十分糟糕。若是在模擬器上運行的很卡,也可能在真機上很平滑。你沒法肯定。

另外一件重要的事情就是性能測試必定要用發佈配置,而不是調試模式。由於當用發佈環境打包的時候,編譯器會引入一系列提升性能的優化,例如去掉調試符號或者移除並從新組織代碼。你也能夠本身作到這些,例如在發佈環境禁用NSLog語句。你只關心發佈性能,那纔是你須要測試的點。

最後,最好在你支持的設備中性能最差的設備上測試:若是基於iOS6開發,這意味着最好在iPhone 3GS或者iPad2上測試。若是可能的話,測試不一樣的設備和iOS版本,由於蘋果在不一樣的iOS版本和設備中作了一些改變,這也可能影響到一些性能。例如iPad3明顯要在動畫渲染上比iPad2慢不少,由於渲染4倍多的像素點(爲了支持視網膜顯示)。

保持一致的幀率

爲了作到動畫的平滑,你須要以60FPS(幀每秒)的速度運行,以同步屏幕刷新速率。經過基於NSTimer或者CADisplayLink的動畫你能夠下降到30FPS,並且效果還不錯,可是沒辦法經過Core Animation作到這點。若是不保持60FPS的速率,就可能隨機丟幀,影響到體驗。

你能夠在使用的過程當中明顯感到有沒有丟幀,但沒辦法經過肉眼來獲得具體的數據,也無法知道你的作法有沒有真的提升性能。你須要的是一系列精確的數據。

你能夠在程序中用CADisplayLink來測量幀率(就像11章「基於定時器的動畫」中那樣),而後在屏幕上顯示出來,但應用內的FPS顯示並不可以徹底真實測量出Core Animation性能,由於它僅僅測出應用內的幀率。咱們知道不少動畫都在應用以外發生(在渲染服務器進程中處理),但同時應用內FPS計數的確能夠對某些性能問題提供參考,一旦找出一個問題的地方,你就須要獲得更多精確詳細的數據來定位到問題所在。蘋果提供了一個強大的Instruments工具集來幫咱們作到這些。

相關文章
相關標籤/搜索