##CPU VS GPU
關于繪圖和動畫有兩種處理的方式:CPU(中央處理器)和GPU(圖形處理器)。在現代iOS設備中,都有可以運行不同軟件的可編程芯片,但是由于歷史原因,我們可以說CPU所做的工作都在軟件層面,而GPU在硬件層面。
總的來說,我們可以用軟件(使用CPU)做任何事情,但是對于圖像處理,通常用硬件會更快,因為GPU使用圖像對高度并行浮點運算做了優化。由于某些原因,我們想盡可能把屏幕渲染的工作交給硬件去處理。問題在于GPU并沒有無限制處理性能,而且一旦資源用完的話,性能就會開始下降了(即使CPU并沒有完全占用)
大多數動畫性能優化都是關于智能利用GPU和CPU,使得它們都不會超出負荷。于是我們首先需要知道Core Animation是如何在這兩個處理器之間分配工作的。
###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每一幀的渲染,即使這些事情不是你的應用程序可控的。