作者:LaiYoung_
連結:https://juejin.im/post/5ace078cf265da23994ee493
卡頓產生的原因
在VSync訊號到來後,系統圖形服務會透過CADisplayLink等機制通知App,App主執行緒開始在CPU中計算顯示內容,比如檢視的建立、佈局計算、圖片解碼、文字繪製等。隨後CPU會將計算好的內容提交到GPU去,由GPU進行變換、合成、渲染。隨後GPU會把渲染結果提交到幀緩衝區去,等待下一次VSync訊號到來時顯示到螢幕上。由於垂直同步的機制,如果在一個VSync時間內,CPU或者GPU沒有完成內容提交,則那一幀就會被丟棄,等待下一次機會再顯示,而這時顯示屏會保留之前的內容不變。這就是介面卡頓的原因。
在開發中,CPU和GPU中任何一個壓力過大,都會導致掉幀現象,所以在開發時,也需要分別對CPU和GPU壓力進行評估和最佳化。
iOS 裝置中的 CPU & GPU
CPU
載入資源,物件建立,物件調整,物件銷毀,佈局計算,Autolayout,文字計算,文字渲染,圖片的解碼, 影象的繪製(Core Graphics)都是在CPU上面進行的。
GPU
GPU是一個專門為圖形高併發計算而量身定做的處理單元,比CPU使用更少的電來完成工作並且GPU的浮點計算能力要超出CPU很多。
GPU的渲染效能要比CPU高效很多,同時對系統的負載和消耗也更低一些,所以在開發中,我們應該儘量讓CPU負責主執行緒的UI調動,把圖形顯示相關的工作交給GPU來處理,當涉及到光柵化等一些工作時,CPU也會參與進來,這點在後面再詳細描述。
相對於CPU來說,GPU能幹的事情比較單一:接收提交的紋理(Texture)和頂點描述(三角形),應用變換(transform)、混合(合成)並渲染,然後輸出到螢幕上。通常你所能看到的內容,主要也就是紋理(圖片)和形狀(三角模擬的向量圖形)兩類。
CPU 和 GPU 的協作
由上圖可知,要在螢幕上顯示檢視,需要CPU和GPU一起協作,CPU計算好顯示的內容提交到GPU,GPU渲染完成後將結果放到幀快取區,隨後影片控制器會按照VSync訊號逐行讀取幀緩衝區的資料,經過可能的數模轉換傳遞給顯示器顯示。
緩衝機制
iOS使用的是雙緩衝機制。即GPU會預先渲染好一幀放入一個緩衝區內(前幀快取),讓影片控制器讀取,當下一幀渲染好後,GPU會直接把影片控制器的指標指向第二個緩衝器(後幀快取)。當你影片控制器已經讀完一幀,準備讀下一幀的時候,GPU會等待顯示器的VSync訊號發出後,前幀快取和後幀快取會瞬間切換,後幀快取會變成新的前幀快取,同時舊的前幀快取會變成新的後幀快取。
最佳化方案
在YY大神的 iOS 保持介面流暢的技巧中詳細介紹了 CPU 資源消耗原因和解決方案和 GPU 資源消耗原因和解決方案,這裡麵包括了開發中的大部分場景,可以幫助我們快速定位卡頓的原因,迅速解決卡頓。
下麵是一些常見的最佳化方案!
TableViewCell 復用
在cellForRowAtIndexPath:回呼的時候只建立實體,快速傳回cell,不繫結資料。在willDisplayCell: forRowAtIndexPath:的時候系結資料(賦值)。
高度快取
在tableView滑動時,會不斷呼叫heightForRowAtIndexPath:,當cell高度需要自適應時,每次回呼都要計算高度,會導致 UI 卡頓。為了避免重覆無意義的計算,需要快取高度。
怎麼快取?
-
字典,NSCache。
-
UITableView-FDTemplateLayoutCell
檢視層級最佳化
不要動態建立檢視
-
在記憶體可控的前提下,快取subview。
-
善用hidden。
減少檢視層級
-
減少subviews個數,用layer繪製元素。
-
少用clearColor,maskToBounds,陰影效果等。
減少多餘的繪製操作
圖片
-
不要用JPEG的圖片,應當使用PNG圖片。
-
子執行緒預解碼(Decode),主執行緒直接渲染。因為當image沒有Decode,直接賦值給imageView會進行一個Decode操作。
-
最佳化圖片大小,儘量不要動態縮放(contentMode)。
-
盡可能將多張圖片合成為一張進行顯示。
減少透明 view
使用透明view會引起blending,在iOS的圖形處理中,blending主要指的是混合畫素顏色的計算。最直觀的例子就是,我們把兩個圖層疊加在一起,如果第一個圖層的透明的,則最終畫素的顏色計算需要將第二個圖層也考慮進來。這一過程即為Blending。
會導致blending的原因:
-
UIView的alpha<1。
-
UIImageView的image含有alpha channel(即使UIImageView的alpha是1,但只要image含有透明通道,則仍會導致blending)。
為什麼blending會導致效能的損失?
原因是很直觀的,如果一個圖層是不透明的,則系統直接顯示該圖層的顏色即可。而如果圖層是透明的,則會引起更多的計算,因為需要把另一個的圖層也包括進來,進行混合後的顏色計算。
-
opaque設定為YES,減少效能消耗,因為GPU將不會做任何合成,而是簡單從這個層複製。
減少離屏渲染
離屏渲染指的是在影象在繪製到當前螢幕前,需要先進行一次渲染,之後才繪製到當前螢幕。
OpenGL中,GPU螢幕渲染有以下兩種方式:
-
On-Screen Rendering即當前螢幕渲染,指的是GPU的渲染操作是在當前用於顯示的螢幕緩衝區中進行。
-
Off-Screen Rendering即離屏渲染,指的是GPU在當前螢幕緩衝區以外新開闢一個緩衝區進行渲染操作。
為什麼離屏渲染會發生卡頓?主要包括兩方面內容:
-
建立新的緩衝區。
-
背景關係切換,離屏渲染的整個過程,需要多次切換背景關係環境(CPU渲染和GPU切換),先是從當前螢幕(On-Screen)切換到離屏(Off-Screen);等到離屏渲染結束以後,將離屏緩衝區的渲染結果顯示到螢幕上又需要將背景關係環境從離屏切換到當前螢幕。而背景關係環境的切換是要付出很大代價的。
設定了以下屬性時,都會觸發離屏渲染:
-
layer.shouldRasterize,光柵化
-
layer.mask,遮罩
-
layer.allowsGroupOpacity為YES,layer.opacity的值小於1.0
-
layer.cornerRadius,並且設定layer.masksToBounds為YES。可以使用剪下過的圖片,或者使用layer畫來解決。
-
layer.shadows,(表示相關的shadow開頭的屬性),使用shadowPath代替。
-
兩種不同方式來繪製陰影:不使用shadowPath
使用shadowPath
效能差別,如下圖:
離屏渲染的最佳化建議
-
使用ShadowPath指定layer陰影效果路徑。
-
使用非同步進行layer渲染(Facebook開源的非同步繪製框架AsyncDisplayKit)。
-
設定layer的opaque值為YES,減少複雜圖層合成。
-
儘量使用不包含透明(alpha)通道的圖片資源。
-
儘量設定layer的大小值為整形值。
-
直接讓美工把圖片切成圓角進行顯示,這是效率最高的一種方案。
-
很多情況下使用者上傳圖片進行顯示,可以在客戶端處理圓角。
-
使用程式碼手動生成圓角image設定到要顯示的View上,利用UIBezierPath(Core Graphics框架)畫出來圓角圖片。
合理使用光柵化 shouldRasterize
光柵化是把GPU的操作轉到CPU上,生成點陣圖快取,直接讀取復用。
優點:
-
CALayer會被光柵化為bitmap,shadows、cornerRadius等效果會被快取。
缺點:
-
更新已經光柵化的layer,會造成離屏渲染。
-
bitmap超過100ms沒有使用就會移除。
-
受系統限制,快取的大小為 2.5X Screen Size。
shouldRasterize適合靜態頁面顯示,動態頁面會增加開銷。如果設定了shouldRasterize為YES,那也要記住設定rasterizationScale為contentsScale。
非同步渲染
在子執行緒繪製,主執行緒渲染。例如 VVeboTableViewDemo
理性使用-drawRect:
大家或許感到奇怪,有不少開發者在發有關效能最佳化的部落格當中指出使用-drawRect:來最佳化效能。但是我這裡不太建議大家未經思考的使用-drawRect:方法。原因如下:
當你使用UIImageView在載入一個檢視的時候,這個檢視雖然依然有CALayer,但是卻沒有申請到一個後備的儲存,取而代之的是使用一個使用螢幕外渲染,將CGImageRef作為內容,並用渲染服務將圖片資料繪製到幀的緩衝區,就是顯示到螢幕上,當我們滾動檢視的時候,這個檢視將會重新載入,浪費效能。所以對於使用-drawRect:方法,更傾向於使用CALayer來繪製圖層。因為使用CALayer的-drawInContext:,Core Animation將會為這個圖層申請一個後備儲存,用來儲存那些方法繪製進來的點陣圖。那些方法內的程式碼將會執行在CPU上,結果將會被上傳到GPU。這樣做的效能更為好些。
靜態介面建議使用-drawRect:的方式,動態頁面不建議。
按需載入
-
區域性掃清,掃清一個cell就能解決的,堅決不掃清整個section或者整個tableView,掃清最小單元元素。
-
利用runloop提高滑動流暢性,在滑動停止的時候再載入內容,像那種一閃而過的(快速滑動),就沒有必要載入,可以使用預設的佔位符填充內容。
關於效能測試
在出現影象效能問題,滑動,動畫不夠流暢之後,我們首先要做的就是定位出問題的所在。而這個過程並不是隻靠經驗和窮舉法探索,我們應該用有脈絡,有順序的科學的手段進行探索
。
首先,我們要有一個定位問題的樣式。我們可以按照這樣的順序來逐步定位,發現問題。
-
定位幀率,為了給使用者流暢的感受,我們需要保持幀率在60幀左右。當遇到問題後,我們首先檢查一下幀率是否保持在60幀。
-
定位瓶頸,究竟是CPU還是GPU。我們希望佔用率越少越好,一是為了流暢性,二也節省了電力。
-
檢查有沒有做無必要的CPU渲染,例如有些地方我們重寫了drawRect:,而其實是我們不需要也不應該的。我們希望GPU負責更多的工作。
-
檢查有沒有過多的離屏渲染,這會耗費GPU的資源,像前面已經分析的到的。離屏渲染會導致GPU需要不斷地onScreen和offscreen進行背景關係切換。我們希望有更少的離屏渲染。
-
檢查我們有無過多的Blending,GPU渲染一個不透明的圖層更省資源。
-
檢查圖片的格式是否為常用格式,大小是否正常。如果一個圖片格式不被GPU所支援,則只能透過CPU來渲染。一般我們在iOS開發中都應該用PNG格式,之前閱讀過的一些資料也有指出蘋果特意為PNG格式做了渲染和壓縮演演算法上的最佳化。
-
檢查是否有耗費資源多的View或效果,我們需要合理有節制的使用。
-
最後,我們需要檢查在我們View層級中是否有不正確的地方。例如有時我們不斷的新增或移除View,有時就會在不經意間導致bug的發生。
測試工具:
-
Core Animation,Instruments裡的圖形效能問題的測試工具。
-
view debugging,Xcode 自帶的,檢視層級。
-
reveal,檢視層級。
參考文章
-
繪製畫素到螢幕上
-
iOS圖形原理與離屏渲染,在1.4.1中,這也是為什麼 CALayer 有一個叫做 opaque 的屬性了。如果這個屬性為 NO,GPU 將不會做任何合成,而是簡單從這個層複製,不需要考慮它下方的任何東西(因為都被它遮擋住了)。中的opaque屬性為NO,GPU將不會做任何合成,這句話時錯誤的,應該是為YES,GPU才不會做任何合成。
-
iOS 保持介面流暢的技巧
-
Advanced Graphics and Animations for iOS Apps(session 419)
-
使用 ASDK 效能調優 – 提升 iOS 介面的渲染效能
-
Designing for iOS: Graphics & Performance
-
iOS離屏渲染之最佳化分析
-
iOS檢視渲染以及效能最佳化總結
-
iOS 離屏渲染
-
深刻理解移動端最佳化之離屏渲染
-
iOS 流暢度效能最佳化、CPU、GPU、離屏渲染
-
iOS 圖形效能最佳化錦集
-
離屏渲染最佳化詳解:實體示範+效能測試
朋友會在“發現-看一看”看到你“在看”的內容