歡迎光臨
每天分享高質量文章

iOS 性能優化總結

作者: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提高滑動流暢性,在滑動停止的時候再加載內容,像那種一閃而過的(快速滑動),就沒有必要加載,可以使用預設的占位符填充內容。

關於性能測試

在出現圖像性能問題,滑動,動畫不夠流暢之後,我們首先要做的就是定位出問題的所在。而這個過程並不是只靠經驗和窮舉法探索,我們應該用有脈絡,有順序的科學的手段進行探索

首先,我們要有一個定位問題的樣式。我們可以按照這樣的順序來逐步定位,發現問題。

 

  1. 定位幀率,為了給用戶流暢的感受,我們需要保持幀率在60幀左右。當遇到問題後,我們首先檢查一下幀率是否保持在60幀。

  2. 定位瓶頸,究竟是CPU還是GPU。我們希望占用率越少越好,一是為了流暢性,二也節省了電力。

  3. 檢查有沒有做無必要的CPU渲染,例如有些地方我們重寫了drawRect:,而其實是我們不需要也不應該的。我們希望GPU負責更多的工作。

  4. 檢查有沒有過多的離屏渲染,這會耗費GPU的資源,像前面已經分析的到的。離屏渲染會導致GPU需要不斷地onScreen和offscreen進行背景關係切換。我們希望有更少的離屏渲染。

  5. 檢查我們有無過多的Blending,GPU渲染一個不透明的圖層更省資源。

  6. 檢查圖片的格式是否為常用格式,大小是否正常。如果一個圖片格式不被GPU所支持,則只能通過CPU來渲染。一般我們在iOS開發中都應該用PNG格式,之前閱讀過的一些資料也有指出蘋果特意為PNG格式做了渲染和壓縮演算法上的優化。

  7. 檢查是否有耗費資源多的View或效果,我們需要合理有節制的使用。

  8. 最後,我們需要檢查在我們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 圖形性能優化錦集

  • 離屏渲染優化詳解:實體示範+性能測試

已同步到看一看
赞(0)

分享創造快樂