Metal新特性:大幅度提升iOS端效能

阿里巴巴淘系技術發表於2020-07-20

前言

Metal 是一個和 OpenGL ES 類似的面向底層的圖形程式設計介面,通過使用相關的 api 可以直接操作 GPU ,最早在 2014 年的 WWDC 的時候釋出。Metal 是 iOS 平臺獨有的,意味著它不能像 OpenGL ES 那樣支援跨平臺,但是它能最大的挖掘蘋果移動裝置的 GPU 能力,進行復雜的運算,像 Unity 等遊戲引擎都通過 Metal 對 3D 能力進行了優化, App Store 還有相應的運用 Metal 技術的遊戲專題。

阿里巴巴淘系技術部的閒魚團隊是比較早在客戶端側選擇Flutter方案的技術團隊,當前的閒魚工程裡也是一個較為複雜的Native-Flutter混合工程。作為一個2C的應用,效能和使用者體驗一直是閒魚技術團隊在開發中比較關注的點。而Metal這樣的直接操作GPU的底層介面無疑會給閒魚技術團隊突破效能瓶頸提供一些新的思路。

下面會詳細闡述一下這次大會Metal相關的新特性,以及對於閒魚技術和整個淘系技術來說,這些新特性帶來了哪些技術啟發與思考。

( WWDC 2020精彩內容思否專欄:https://segmentfault.com/blog...  

本篇內容來自於阿里巴巴淘系技術部,無線開發工程師岑彧。
更多精彩內容可關注【淘系技術】公眾號。)

Metal相關新特性

1.Harness Apple GPUs with Metal

這一章其實主要介紹的是Apple GPU的在圖形渲染上的原理和工作流,是一些比較底層的硬體原理。當我們使用Metal進行App或者是遊戲的構建的時候,Metal會利用GPU的tile-based deferred rendering (TBDR)架構給應用和遊戲帶來非常可觀的效能提升。這一章主要就是介紹GPU的的架構和能力,以及TBDR架構進行影像渲染的原理和流程。總之就是號召開發者們使用Metal來構建應用和遊戲。因為這個session沒有涉及到上層的軟體開發,就不對視訊的具體內容進行贅述了。詳情可見:Harness Apple GPUs with Metal

2.Optimize Metal apps and games with GPU counters

這一章主要介紹了Xcode中的GPU效能分析工具Instrument,這個工具現在已經支援了GPU的效能分析。然後從多個方面分析了GPU的效能瓶頸,以及效能瓶頸出現時的優化點。總體來說就是通過效能分析工具來優化我們的App或者遊戲,讓整個畫面更加流暢。整個章節主要分為五個部分:

1.總體介紹

這個環節主要是快速回顧了一下Apple的GPU的架構和渲染流程。然後因為很多渲染任務都需要在不同的硬體單元上進行,例如ALU和TPU。他們對不同的吞吐量有著不同的度量。有很多GPU的效能指標需要被考慮,所以推出了GPU效能計數器。這個計數器可能測量到GPU的利用率,過高和過低都會造成我們的渲染效能瓶頸。關於計數器的具體使用,參考官方的video效果會更好:Optimize Metal apps and games with GPU counters(6:37~9:57),主要使用了Instrument工具,關於工具的全面詳細的使用可以參考WWDC19的session videoGetting Started with Instruments

2.效能瓶頸分析

這一章主要介紹了造成GPU效能瓶頸的各個方面以及它們的優化點。主要分為六個方面,如下圖所示:

1.Arithmetic(運算能力)

GPU中通常通過ALU(Arithmetic Logic Unit)來處理各種運算,例如位操作,關係操作等。他是著色器核心的一部分。在這裡一些複雜的操作或者是高精度的浮點運算都會造成一些效能瓶頸,所以給出以下建議來進行優化:

如上圖所示,我們可以使用近似或者是查詢表的方式來替換複雜的運算。此外,我們可以將全精度的浮點數替換為半精度的浮點數。儘量避免隱式轉換,避免32位浮點數的輸入。以及確保所有的著色器都使用Metal的“-ffast-math”來進行編譯。

2.Texture Read and Write

GPU通過Texture Processing Unit來處理紋理的讀寫操作。當然在讀寫的過程中也會遇到一些效能瓶頸問題。這裡從讀和寫兩個部分分別來給出優化點:

1.Read

如上圖所示,我們可以嘗試使用mipmaps。此外,可以考慮更改過濾選項。例如,使用雙線性代替三線性,降低畫素大小。確保使用了紋理壓縮,對Asset使用塊壓縮(如ASTC),對執行時生成的紋理使用無損紋理壓縮。

2.Write

如上圖所示,我們應該注意到畫素的大小,以及每個畫素中唯一MSAA樣本的數量。此外,可以嘗試一些優化一些邏輯寫法。

3.Tile Memory Load and Store

圖塊記憶體是一組儲存Thread Group和ImageBlock資料的高效能記憶體。當從ImageBlock或是Threadgroup讀取或寫入畫素資料時,比如在使用Tile著色器時或者是計算分派時,可以訪問到Tile記憶體。那當使用GPU效能計數器發現這個方面的效能瓶頸時,我們可以如下圖所示進行優化。

考慮減少threadgroup的並行,或者是SIMD/Quadgroup操作。此外,確保將執行緒組的記憶體分配和訪問對齊到16位元組。最後,可以考慮重新排序記憶體訪問模式。

4.Buffer Read and Write

在Metal中,緩衝區只被著色器核心訪問。在這個地方發現了效能瓶頸。我們可以如下圖所示進行優化:

可以更大力度的壓縮打包資料,例如使用例如packed_half3這樣小的型別。此外,可以嘗試向量化載入和儲存。例如使用SIMD型別。避免暫存器溢位,以及可以使用紋理來平衡工作負載。

5.GPU Last Level Cache

如果在這個方面,我們的GPU效能計數器顯示一個過高的值。我們可以如下圖這樣優化:

如果紋理或者是快取區也同樣顯示一個過高的值,我們可以把這個優化放到第一優先順序。我們可以考慮減小工作集的大小。如果Shader正在使用Device Atomics,我們可以嘗試重構我們的程式碼來使用Threadgroup Atomics。

6.Fragment Input Interpolation

分段輸入插值。分段輸入在渲染階段由著色器核心進行插值。著色器核心有一個專用的分段輸入插值器。這個是比較固定和高精度的功能。我們能優化的點不多,如下圖所示:

儘可能的移除傳遞給分段著色器的頂點屬性。

3.記憶體頻寬

記憶體頻寬也是影響我們GPU效能的一個重要因素。如果在GPU效能計數器的記憶體頻寬模組看到一個很高的值。我們就應該如下圖所示來進行優化:

如果紋理和快取區也同樣顯示比較高的值,那優化優先順序應該排到第一位。優化方案也是較少Working Set的大小。此外,我們應該只載入當前渲染過程需要的資料,只儲存未來渲染過程需要的資料。然後就是確保使用紋理壓縮。

4.Occupancy

如果我們看到整體利用率比較低,這意味著Shader可能已經耗盡了一些內部資源,比如tile或者threadgroup記憶體。也可能是執行緒完成執行的速度比GPU建立新執行緒的速度快。

5.避免重複繪製

我們通過GPU計數器可以統計到重複繪製的區域,我們應該高校使用HSR來避免這樣的重繪。我們可以如圖所示的順序來進行繪製。

3.Build GPU binaries with Metal

這一章主要給開發者們介紹了一種使用Metal的程式設計工作流,可以通過優化Metal的渲染編譯模型來增強渲染管線,這個優化可以在應用程式啟動,特別是首次啟動時大大減少PSO(管線狀態物件)的載入時間。可以讓我們的圖形渲染更加的高效。整個章節主要分為四個部分:

1.Metal的Shader編譯模型概述

眾所周知,Metal Shading Language是Apple為開發者提供的Shader程式語言,Metal會將程式語言編譯成為一個叫做AIR的中間產物,然後AIR會在裝置上進一步編譯,生成每個GPU所需的特定的機器碼。整個過程如下圖所示:

上述過程在每個管線的生命週期中都會發生,當前Apple為了加速管線的重新編譯和重新建立流程,會快取一些Metal的方法變體,但是這個過程還是會造成螢幕的載入耗時過長。而且在當前的這個編譯模型中,應用程式不能在不同的PSO(管線狀態物件)中重用之前生成的機器碼子程式。
所以我們需要一種方法來減少這個整個管線編譯(即原始碼->AIR->GPU二進位制程式碼)的時間成本,還需要一種機制來支援不同PSO之間共享子程式和方法,這樣就不需要將相同的程式碼多次編譯或者是多次載入到記憶體中。這樣開發者們就可以使用這套工具來優化App首次的啟動體驗。

2.Metal二進位制檔案介紹

Metal二進位制檔案就是解決上述需求的方法之一,現在開發者們可以直接使用Metal為二進位制檔案來控制PSO的快取。開發者可以收集已編譯的PSO,然後將它們儲存到裝置中,甚至可以分發到其他相容的裝置中(同樣的GPU和同樣的作業系統),這種二進位制檔案可以看做一種Asset。下面是一些例程和示意圖:

螢幕快照 2020-07-20 上午9.53.14.png

螢幕快照 2020-07-20 上午9.53.53.png

螢幕快照 2020-07-20 上午9.53.53.png

螢幕快照 2020-07-20 上午9.54.31.png

總的來說就是這個Metal二進位制檔案可以提供開發者手動管理管線快取的方法,這樣就可以從一個裝置中獲取這些檔案並部署到其他相容的裝置上,在iOS環境下,極大地減少了第一次安裝遊戲或應用以及裝置重啟後的管道建立時間。可以優化應用的首次啟動體驗和冷啟動體驗。

3.Metal對動態庫的支援

動態庫將允許開發者編寫可重用的庫程式碼,卻可以減少重新編譯程式的時間和記憶體成本,這個特性將會允許開發者將計算著色器和程式庫動態連結。而且和二進位制檔案一樣,動態庫也是可序列化和可轉移的。這也是解決上述需求的方案之一。
在PSO生成的時候,每個應用程式都需要為程式library生成機器碼,而且使用相同的程式庫編譯多個管線會導致生成重複的機器碼。由於大量的編譯和記憶體的增加,這個可能會導致更長的管線載入時間。而動態庫就可以解決這個問題。
Metal Dynamic Library允許開發者以機器碼的形式動態連結,載入和共享工具方法。程式碼可以在多個計算管線中重用,消除了重複編譯和多個相同子程式的儲存。而且這個 MTLDynamicLibrary是可序列化的,可以作為應用程式的Asset使用。MTLDynamicLibrary其實就是多個計算管線呼叫的匯出方法的集合。
大致的工作流程如下:我們首先建立一個MTLLibrary作為我們指定的動態庫,這個可以將我們的metal程式碼編譯為AIR。然後我們呼叫方法makeDynamicLibrary,這個方法需要指定一個唯一的installname,在管線建立時,linker將會使用這個名字來載入動態庫。這個方法可以將我們的動態庫編譯成為機器碼。這就完成了動態庫的建立。
對於動態庫的使用來說:通過設定MTLCompileOptions裡的libraries引數,就可以完成動態庫的載入和使用了。程式碼如下:

螢幕快照 2020-07-20 上午10.04.56.png

4.開發工具介紹

這個部分主要介紹了構建Metal二進位制檔案和構建動態庫的具體工具和方法。以視訊的形式可能會更好的表現,詳情可見:Build GPU binaries with Metal (從22:51開始)

4.Debug GPU-side errors in Metal

這一章主要介紹的是GPU側的bug,當前如果我們的應用程式出現了GPU側的bug,他的錯誤日誌常常都不能讓開發者很直觀的定位到錯誤的程式碼範圍和呼叫棧。所以在最新的Xcode中,增強了關於GPU側的debug機制。可以像在程式碼側發成的錯誤一樣不但能定位到錯誤原因,還有錯誤的呼叫堆疊和各種資訊都可以詳細的檢視到。讓開發者能更好的修復程式碼造成的GPU側的渲染錯誤。

1.Enhanced Command Buffer Errors

這是當前的錯誤日誌上報,我們可以看到GPU側的錯誤日誌不像Api的錯誤日誌一樣可以讓開發者很快的定位到錯誤原因和錯誤的程式碼位置。

而最新的Metal debugging工具就增強了這方面的能力,讓Shader的code也可以像Api程式碼一樣提供錯誤定位和分類能力。

我們通過以下程式碼便可以啟用增強版的commandbuffer錯誤機制

螢幕快照 2020-07-20 上午10.05.27.png

錯誤一共有五種狀態:

我們也可以通過以下程式碼來列印error:

螢幕快照 2020-07-20 上午10.06.00.png

開發者可以在開發時和測試時啟用優化版的錯誤機制

2.Shader Validation

如上圖所示,這個功能可以在GPU側發生渲染錯誤時自動定位和catch到錯誤並定位到程式碼,以及獲取回溯棧幀。

我們可以在Xcode中按照以下流程來開啟這個功能:

1.開啟Metal中的兩個Validation選項

2.開啟issue自動斷點開關並配置型別和分類等選項

Video中用了一個demo來展示整個工作流,具體參見Debug GPU-side errors in Metal(11:25~14:45)大致流程如下圖所示:

這是一個Demo應用程式,很明顯它在渲染上出現了一些異常,但是因為是GPU側的問題,所以開發者很難定位。但是通過上述的工作流開啟Shader Validation之後。

Xcode會自動斷點到發生異常的地方,並展示出異常資訊,這樣就可以極大的提升開發者的錯誤修復效率。

5.Gain insights into your Metal app with Xcode 12

這一章主要講的是Xcode12給Metal App提供了更多除錯和分析的新工具。大致如下圖所示:

主要分為兩個部分:

1.Metal Debugger

這個工具可以讓開發者在App執行時,獲取到想分析和除錯的任何一幀,然後再進入Xcode提供的各種分析介面,總體情況,依賴情況,記憶體,頻寬,GPU,Shader等各種具體的介面來對這一幀進行更加詳細的分析和除錯。整個過程使用視訊的方式可能會更加高效,所以這裡不會進行詳細的贅述和分析。詳情可以參見Gain insights into your Metal app with Xcode 12

2.Metal System Trace

整個工具跟之前提到過的Debugger相比,他的功能主要是讓開發者可以隨著時間的推移來捕獲應用程式的各種資訊和特徵,可以讓開發者很好的除錯一些例如終端,幀丟失,記憶體洩漏等問題。而Debugger主要是對某一幀進行除錯和分析。
他提供了一個叫做編碼時間線的工具,可以讓開發者檢視到GPU在應用執行中的執行各種命令緩衝的情況。然後提供了一個叫做著色器時間線的工具,可以讓開發者檢視到各種著色器在程式碼執行期間執行的過程。然後還有GPU計數器的工具,這個工具我們在前文進行了詳細的分析,主要是用於解決GPU的繪製效能問題的工具。然後最後一個工具就是記憶體分配跟蹤工具,可以讓開發者檢視到應用程式執行過程中各種記憶體的分配和釋放,可以幫助開發者解決記憶體洩漏問題或者是降低應用記憶體佔用。

技術啟發與思考

WWDC 20關於Metal的Session中,比較重要的就是官方提供了很多可供開發者進行GPU級別的除錯工具以及效能分析工具。給比較成熟龐大而複雜的工程突破效能瓶頸,提供更加優秀的使用者體驗提供了一些思路。

閒魚作為一個電商類App,隨著功能和增多和以及工程的複雜化,在所難免的會遇到效能瓶頸,而閒魚團隊當前面對挑戰的方式是從工程級別來進行優化。從Flutter的角度來看,WWDC 20 對於Metal的除錯工具和效能分析工具的完善,無疑提供了更多的優化思路。這為未來執行在iOS上的應用的調優和突破效能瓶頸帶來了新的思路和可能性。

對於跨平臺框架,Apple有自家的SwiftUI,這也是此次大會的重點專案。不過無論是Flutter,還是SwiftUI,大家最後對應用的效能瓶頸突破和優化一定是殊途同歸的,也就是深入到GPU級別來進行開發和除錯以及效能分析。對於未來的客戶端開發人員,理解GPU和進行GPU級別的程式設計肯定是不可或缺的技能點之一。

( WWDC 2020精彩內容思否專欄:https://segmentfault.com/blog...  

本篇內容來自於阿里巴巴淘系技術部,無線開發工程師岑彧。
更多精彩內容可關注【淘系技術】公眾號。)

相關文章