Unity DOTS 走馬觀花
簡單介紹 Data-Oriented Technology Stack (DOTS, 資料導向型技術棧) ,其包含了 C# Job System、the Entity Component System (ECS) 和 Burst。
特點
DOTS 要實現的特點有:
- 效能的準確性。我們希望的效果是:如果迴圈因為某些原因無法向量化(Vectorization),它應該會出現編譯器錯誤,而不是使程式碼執行速度慢8倍,並得到正確結果,完全不報錯。
- 跨平臺架構特性。我們編寫的輸入程式碼無論是面向 iOS 系統還是 Xbox,都應該是相同的。
- 我們應該有不錯的迭代迴圈。在修改程式碼時,可以輕鬆檢視為所有架構生成的機器程式碼。機器程式碼“檢視器”應該很好地說明或解釋所有機器指令的行為。
- 安全性。大多數遊戲開發者不把安全性放在很高的優先順序,但我們認為,解決Unity出現記憶體損壞問題是關鍵特性之一。在執行程式碼時應該有一個特別模式,如果讀取或寫入到記憶體界限外或取消引用Null時,它能夠提供我們明確的錯誤資訊。
高效能 C#
當我們使用 C# 語言時,仍然無法控制資料在記憶體中如何進行分佈,但這是我們提升效能的關鍵點。
除此之外,標準庫面向的是“堆上的物件”和“具有其它物件指標引用的物件”。
也就是意味著,當處理效能敏感程式碼時,我們可以放棄使用大部分標準庫,例如:Linq、StringFormatter、List、Dictionary。禁止記憶體分配,即不使用類,只使用結構、對映、垃圾回收器和虛擬呼叫,並新增可使用的部分新容器,例如:NativeArray 和其他集合型別。
我們可以在越界訪問時得到錯誤和錯誤資訊,以及使用 C++ 程式碼時的偵錯程式支援和編譯速度。我們通常把該子集稱為高效能 C# 或 HPC#。
它可以被總結為:
- 大部分的原始型別(float、int、uint、short、bool...),enums,structs 和其他型別的指標
- 集合:用
NavtiveArray<T>
代替T[]
- 所有的控制流語句(除了 try、finally、foreach、using)
- 對
throw new XXXException(...)
給予基礎支援
Burst
Unity 構建了名為 Burst 的程式碼生成器和編譯器。
Burst 有時執行速度比 C++ 快,有時也會比 C++ 慢。後面的情況源於效能問題,Unity 已經開始著手解決。
當使用 C# 時,我們對整個流程有完整的控制,包括從原始碼編譯到機器程式碼生成,如果有我們不想要的部分,我們會找到並修復它。我們會逐漸把 C++ 語言的效能敏感程式碼移植為 HPC# 程式碼,這樣會更容易得到想要的效能,更難出現 Bug,更容易進行處理。
如果 Asset Store 資源外掛的開發者在資源中使用 HPC# 程式碼,資源外掛在執行時程式碼會執行得更快。除此之外,高階使用者也會通過使用 HPC# 編寫出自定義高效能程式碼而受益。
ECS Track: Deep Dive into the Burst Compiler - Unite LA
Burst 對於 HPC# 更詳細的支援可以在下面找到:
解決的多執行緒問題
C++ 和 C# 都無法為開發者編寫執行緒安全程式碼提供太多幫助。即使在今天,擁有多個核心遊戲消費級硬體發展至今已經過去了十年,但依舊很難有效處理使用多個核心的程式。
資料衝突,不確定性和死鎖是使多執行緒程式碼難以編寫的挑戰。Unity 想要的特性是“確保程式碼呼叫的函式和所有內容不會在全域性狀態下讀取或寫入”。Unity 希望應該讓編譯器丟擲錯誤來提醒,而不是屬於“程式設計師應遵守的準則”,Burst 則會提供編譯器錯誤。
Unity 鼓勵 Unity 使用者編寫 “Jobified” 程式碼:將所有需要發生的資料轉換劃分為 Job。
每個 Job 都具有“功能性”,因為 Job 沒有副作用。 Job 會明確指定使用的只讀緩衝區和讀寫緩衝區,嘗試訪問其它資料會出現編譯器錯誤。
Job 排程程式會確保在 Job 執行時,任何程式都不會寫入只讀緩衝區。我們會確保在 Job 執行時,任何程式都不會讀取讀寫緩衝區。
如果排程的 Job 違反了這些規則,我們會得到執行時錯誤。這種錯誤不僅在競態條件下得到,錯誤資訊會說明,你正在嘗試排程的 Job 想要讀取緩衝區 A,但你之前已經排程了會寫入緩衝區 A 的 Job ,所以如果想要執行該操作,需要把之前的 Job 指定為依賴。
深入棧
由於能夠處理所有元件,我們可以使這些元件瞭解各自的存在。例如:向量化(Vectorization)無法進行的常見情況是,編譯器無法確保二個指標不指向相同的記憶體,即混淆情況(Alias)。
而兩個集合庫中的 NativeArray 從不會混淆,我們可以在 Burst 中運用這個知識,使它不會由於害怕兩個陣列指標指向相同記憶體而放棄優化(Alias)。Alias 的問題在 Unity GDC 中也有一個演講提到過:Unity at GDC - C# to Machine Code
Unity 還編寫了 Unity.Mathemetics 數學庫,提供了很多像 Shader 程式碼的資料結構。Burst 也能和這數學庫很好的工作,未來 Burst 將能夠為 math.sin()
等計算作出犧牲精度的優化。
對於 Burst 而言,math.sin()
不僅是要編譯的 C# 方法,Burst 還能理解出 sin()
的三角函式屬性,同時知道 x 值較小時會出現 sin(x)
等於 x 的情況,並瞭解它能替換為泰勒級數展開,以便犧牲特定精度。
跨平臺和架構的浮點準確性是 Burst 未來的目標。
Entity Component System
Unity 一直以元件的概念為中心,例如:我們可以新增 Rigidbody 元件到遊戲物件上,使物件能夠向下掉落。我們也可以新增 Light 元件到遊戲物件上,使它可以發射光線。我們新增 AudioEmitter 元件,可以使遊戲物件發出聲音。
我們實現元件系統的方法並沒有很好地演變。我們過去使用物件導向的思維編寫元件系統。元件和遊戲物件都是“大量使用 C++ 程式碼”的物件,建立或銷燬它們需要使用互斥鎖修改“id 到物件指標”的全域性列表。
通過使用面向資料的思維方式,我們可以更好地處理這種情況。我們可以保留使用者眼中的優良特性,即只需新增元件就可以實現功能,而同時通過新元件系統取得出色的效能和並行效果。
這個全新的元件系統就是實體元件系統 ECS。簡單來說,如今我們對遊戲物件進行的操作可用於處理新系統的實體,元件仍稱作元件。那麼區別是什麼?區別在於資料佈局。
class Orbit : MonoBehaviour
{
public Transform _objectToOrbitAround;
void Update()
{
var currentPos = GetComponent<Transform>().position;
var targetPos = _objectToOrbitAround.position;
GetComponent<RigidBody>().velocity += SomehowSteerTowards(currentPos, targetPos)
}
}
這種模式會被反覆使用:元件必須找到相同遊戲物件上的一個或多個元件,然後給它讀取或寫入數值。
這種方法存在很多問題:
Update()
方法被一個單獨的 Orbit 元件呼叫,下次呼叫Update()
的會是完全不同的元件,它很可能造成程式碼從快取移出。Update()
必須使用GetComponent()
來找到 Rigidbody 元件。該元件也可以被快取起來,但必須保證 Rigidbody 元件不被銷燬。- 我們要處理的其它元件位於記憶體中完全不同的位置。
ECS 使用的資料佈局會把這些情況看作一種非常常見的模式,並優化記憶體佈局,使類似操作更加快捷。
傳統記憶體佈局
離散的資料導致搜尋效率十分低下,還有 Cache Miss 的問題,這個問題可以參考下面的連結:
ECS 資料佈局
原型(Archetype)
ECS 會在記憶體中對帶有相同元件(Component)集的所有實體(Entity)進行組合。ECS 把這類元件集稱為原型(Archetype)。
下圖的原型就是由 Position 元件、Velocity 元件、Rigidbody 元件和 Renderer 元件組成的。
如果一個實體只有三個元件(不同於前面提到的原型),那麼那三個元件就組成了一個新的原型。
下面的圖來自 Unite LA 的一次演講的講義, 很遺憾那次演講沒有錄製下來。講義可以在這裡找到。
ECS 以 16k 大小的塊(Chunk)來分配記憶體,每個塊僅包含單個原型中所有實體的元件資料。
一個帖子中有人提供了更加形象的記憶體佈局圖,例如上半部分的原型由 Position 元件和 Rock 元件組成,其中整個原型佔了一個塊(Chunk),兩個元件的資料分別存在兩個陣列中,裡面還帶著元件資料對應的實體的資訊。
每個原型都有一個 Chunks 塊列表,用來儲存原型的實體。我們會迴圈所有塊,並在每個塊中,對緊湊的記憶體進行線性迴圈處理,以讀取或寫入元件資料。該線性迴圈會對每個實體執行相同的程式碼,同時為 Burst 創造向量化(Vectorization,可以參考 StackOverflow 的問題)處理的機會。
每個塊會被安排好記憶體中的位置,以便於快速從記憶體得到想要的資料,詳情可以參考下面的文章。
Unity2018 ECS框架Entities原始碼解析(二)元件與Chunk的記憶體佈局 - 大鵬的專欄 - CSDN部落格
實體(Entity)
實體是什麼?實體只是一個 32 位的整數 key (和一些額外的資料例如 index 和 version 實體版本,不過在這裡不重要),所以除了實體的元件資料外,不必為實體儲存或分配太多記憶體。實體可以實現遊戲物件的所有功能,甚至更多功能,因為實體非常輕量。
實體的效能消耗很低,所以我們可以把實體用在不適合遊戲物件的情況,例如:為粒子系統內的每個單獨粒子使用一個實體。
實體本身不是物件,也不是一個容器,它的作用是把其元件的資料關聯到一起。
系統(System)
我們不必使用使用者的 Update 方法搜尋元件,然後在執行時對每個例項進行操作,使用 ECS 時我們只需靜態地宣告:我想對同時附帶 Velocity 元件和 Rigidbody 元件的所有實體進行操作。為了找到所有實體,我們只需找到所有符合特定“元件搜尋查詢”的原型即可,而這個過程就是由系統(System)來完成的。
很多情況下,這個過程會分成多個 Job ,使處理ECS元件的程式碼達到幾乎100%的核心利用率。ECS 會完成所有工作,我們只需要提供對每個實體執行的程式碼即可。我們也可以手動處理塊迭代過程(IJobChunk)。
當我們從實體新增或移除元件時,ECS會切換原型。我們會把它從當前塊移動到新原型的塊,然後交換之前塊的最後實體來“填補空缺”。
在 ECS 中,我們還要靜態宣告要對元件資料進行什麼處理,是 ReadOnly 只讀還是 ReadWrite 讀寫。通過確定僅對 Position 元件進行讀取,ECS 可以更高效地排程 Job ,其它需要讀取 Position 元件的 Job 不必進行等待。
這種資料佈局也處理了 Unity 長期以來的困擾,即:載入時間和序列化的效能。為大型場景載入或流式處理 ECS 資料時,主要的操作是從硬碟載入和使用原始位元組。
API 可用性和樣板程式碼
對 ECS 的常見觀點是:ECS 需要編寫很多程式碼。因此,實現想要的功能需要處理很多樣板程式碼。現在針對移除多數樣板程式碼需求的大量改進即將推出,這些改進會使開發者更簡單地表達自己的目的。
我們暫時沒有實現太多這類改進,因為我們現在正專注於處理基礎效能,但我們知道:太多樣板程式碼對 ECS 遊戲程式碼沒有好處,我們不能讓編寫 ECS 程式碼比編寫 MonoBehaviour 更麻煩。
Project Tiny 已經實現了部分改進,例如:基於 lambda 函式的迭代 API。
最後
由於自己空閒時間不多,只能囫圇吞棗地拼湊出這樣一篇筆記。上面大部分文字都是來自 Unity 的博文介紹,自己加了其他的內容幫助理解。本文對 ECS 的理論介紹幾乎沒有,Job System 也沒有過多的介紹,不過我相信走過一遍文章之後,能更加容易理解 Unity ECS Sample 中的示例程式碼。
參考
相關文章
- Microservices==>Service Mesh==>Serverless,走馬觀花ROSServer
- 走馬觀花-Dagger2 - @Inject 和 @Component
- 面試周連續劇之走馬觀花面試
- 走馬
- Codeforces2020D Connect the Dots(觀察 + 並查集 + 差分)並查集
- Unity——觀察者模式Unity模式
- TextView走馬燈TextView
- Unity GDC 2019 Keynote精彩要點:次時代圖形、實時光線追蹤、DOTSUnity
- 17、Connect-the-dots(VulnHub)
- 純JS實現走馬燈JS
- markevery can control which dots to be plotted
- 世界AI大會三馬縱論:馬雲樂觀、馬斯克悲觀,馬化騰提了個大危害AI馬斯克
- 騰訊天美GDC分享:千人同屏戰鬥,Unity DOTS在《重返帝國》中的應用Unity
- 使用 Flutter 實現一個走馬燈佈局Flutter
- Element-Ui元件(四十二)Carousel 走馬燈UI元件
- 052、走馬川行奉送封大夫出師西征
- 亞馬遜如何趕走煩人的跟賣亞馬遜
- 花小錢、辦大事,2024年Unity遊戲行業趨勢解讀Unity遊戲行業
- 突破從0到1後,鮮花電商2.0時代怎麼走?
- 小學數學學習:神奇的走馬燈數 142857
- element-ui Carousel 走馬燈原始碼分析整理筆記(十一)UI原始碼筆記
- 外觀模式(Facade模式)詳解——小馬同學@Tian模式
- Vue.js+Element-UI走馬燈圖片自適應實踐Vue.jsUI
- 程式人生 | 春風得意馬蹄疾,一日看盡長安花
- “您的主機已被接管!”新型 JavaScript 遠控木馬花樣來襲JavaScript
- Unity——Js和Unity互相呼叫UnityJS
- 055、韋諷錄事宅觀曹將軍畫馬圖
- 亞馬遜無貨源店群模式,能走多遠? YMX973亞馬遜模式
- 北京旅遊:走勢頗為蹊蹺 當前的強勢會否"曇花一現"IFP
- 亞馬遜成全球最大廣告主:去年每分鐘花費約21000美元亞馬遜
- 我先走? 你先走?
- 傳智播客黑馬.NET+Unity3D 遊戲開發視訊教程Unity3D遊戲開發
- environmentmap in unityUnity
- unity xchartsUnity
- Unity元件Unity元件
- unity backbufferUnity
- [Unity]StringBuilderUnityUI
- 畢馬威:2023年一季度中國經濟觀察