溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Unity中DOTS要實(shí)現(xiàn)的特點(diǎn)有哪些

發(fā)布時間:2022-01-05 14:11:18 來源:億速云 閱讀:150 作者:小新 欄目:大數(shù)據(jù)

小編給大家分享一下Unity中DOTS要實(shí)現(xiàn)的特點(diǎn)有哪些,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

DOTS 要實(shí)現(xiàn)的特點(diǎn)有:

  • 性能的準(zhǔn)確性。我們希望的效果是:如果循環(huán)因?yàn)槟承┰驘o法向量化(Vectorization,可以參考 StackOverflow 的問題),它應(yīng)該會出現(xiàn)編譯器錯誤,而不是使代碼運(yùn)行速度慢8倍,并得到正確結(jié)果,完全不報錯。

  • 跨平臺架構(gòu)特性。我們編寫的輸入代碼無論是面向 iOS 系統(tǒng)還是 Xbox,都應(yīng)該是相同的。

  • 我們應(yīng)該有不錯的迭代循環(huán)。在修改代碼時,可以輕松查看為所有架構(gòu)生成的機(jī)器代碼。機(jī)器代碼“查看器”應(yīng)該很好地說明或解釋所有機(jī)器指令的行為。

  • 安全性。大多數(shù)游戲開發(fā)者不把安全性放在很高的優(yōu)先級,但我們認(rèn)為,解決Unity出現(xiàn)內(nèi)存損壞問題是關(guān)鍵特性之一。在運(yùn)行代碼時應(yīng)該有一個特別模式,如果讀取或?qū)懭氲絻?nèi)存界限外或取消引用Null時,它能夠提供我們明確的錯誤信息。

高性能 C

當(dāng)我們使用 C# 語言時,仍然無法控制數(shù)據(jù)在內(nèi)存中如何進(jìn)行分布,但這是我們提升性能的關(guān)鍵點(diǎn)。

除此之外,標(biāo)準(zhǔn)庫面向的是“堆上的對象”和“具有其它對象指針引用的對象”。

也就是意味著,當(dāng)處理性能敏感代碼時,我們可以放棄使用大部分標(biāo)準(zhǔn)庫,例如:Linq、StringFormatter、List、Dictionary。禁止內(nèi)存分配,即不使用類,只使用結(jié)構(gòu)、映射、垃圾回收器和虛擬調(diào)用,并添加可使用的部分新容器,例如:NativeArray 和其他集合類型。

我們可以在越界訪問時得到錯誤和錯誤信息,以及使用 C++ 代碼時的調(diào)試器支持和編譯速度。我們通常把該子集稱為高性能 C# 或 HPC#。

它可以被總結(jié)為:

  • 大部分的原始類型(float、int、uint、short、bool…),enums,structs 和其他類型的指針

  • 集合:用 NavtiveArray<T> 代替 T[]

  • 所有的控制流語句(除了 try、finally、foreach、using)

  • 對 throw new XXXException(...) 給予基礎(chǔ)支持

Burst

Unity 構(gòu)建了名為 Burst 的代碼生成器和編譯器。

Burst 有時運(yùn)行速度比 C++ 快,有時也會比 C++ 慢。后面的情況源于性能問題,Unity 已經(jīng)開始著手解決。

當(dāng)使用 C# 時,我們對整個流程有完整的控制,包括從源代碼編譯到機(jī)器代碼生成,如果有我們不想要的部分,我們會找到并修復(fù)它。我們會逐漸把 C++ 語言的性能敏感代碼移植為 HPC# 代碼,這樣會更容易得到想要的性能,更難出現(xiàn) Bug,更容易進(jìn)行處理。

Unity中DOTS要實(shí)現(xiàn)的特點(diǎn)有哪些

如果 Asset Store 資源插件的開發(fā)者在資源中使用 HPC# 代碼,資源插件在運(yùn)行時代碼會運(yùn)行得更快。除此之外,高級用戶也會通過使用 HPC# 編寫出自定義高性能代碼而受益。

ECS Track: Deep Dive into the Burst Compiler - Unite LA

Burst 對于 HPC# 更詳細(xì)的支持可以在下面找到:

Burst User Guide

解決的多線程問題

C++ 和 C# 都無法為開發(fā)者編寫線程安全代碼提供太多幫助。即使在今天,擁有多個核心游戲消費(fèi)級硬件發(fā)展至今已經(jīng)過去了十年,但依舊很難有效處理使用多個核心的程序。

數(shù)據(jù)沖突,不確定性和死鎖是使多線程代碼難以編寫的挑戰(zhàn)。Unity 想要的特性是“確保代碼調(diào)用的函數(shù)和所有內(nèi)容不會在全局狀態(tài)下讀取或?qū)懭搿?。Unity 希望應(yīng)該讓編譯器拋出錯誤來提醒,而不是屬于“程序員應(yīng)遵守的準(zhǔn)則”,Burst 則會提供編譯器錯誤。

Unity 鼓勵 Unity 用戶編寫 “Jobified” 代碼:將所有需要發(fā)生的數(shù)據(jù)轉(zhuǎn)換劃分為 Job。

每個 Job 都具有“功能性”,因?yàn)?Job 沒有副作用。 Job 會明確指定使用的只讀緩沖區(qū)和讀寫緩沖區(qū),嘗試訪問其它數(shù)據(jù)會出現(xiàn)編譯器錯誤。

Job 調(diào)度程序會確保在 Job 運(yùn)行時,任何程序都不會寫入只讀緩沖區(qū)。我們會確保在 Job 運(yùn)行時,任何程序都不會讀取讀寫緩沖區(qū)。

如果調(diào)度的 Job 違反了這些規(guī)則,我們會得到運(yùn)行時錯誤。這種錯誤不僅在競態(tài)條件下得到,錯誤信息會說明,你正在嘗試調(diào)度的 Job 想要讀取緩沖區(qū) A,但你之前已經(jīng)調(diào)度了會寫入緩沖區(qū) A 的 Job ,所以如果想要執(zhí)行該操作,需要把之前的 Job 指定為依賴。

深入棧

由于能夠處理所有組件,我們可以使這些組件了解各自的存在。例如:向量化(Vectorization)無法進(jìn)行的常見情況是,編譯器無法確保二個指針不指向相同的內(nèi)存,即混淆情況(Alias)。

而兩個集合庫中的 NativeArray 從不會混淆,我們可以在 Burst 中運(yùn)用這個知識,使它不會由于害怕兩個數(shù)組指針指向相同內(nèi)存而放棄優(yōu)化(Alias)。Alias 的問題在 Unity GDC 中也有一個演講提到過:Unity at GDC - C# to Machine Code

Unity 還編寫了 Unity.Mathemetics 數(shù)學(xué)庫,提供了很多像 Shader 代碼的數(shù)據(jù)結(jié)構(gòu)。Burst 也能和這數(shù)學(xué)庫很好的工作,未來 Burst 將能夠?yàn)?nbsp;math.sin() 等計算作出犧牲精度的優(yōu)化。

對于 Burst 而言,math.sin() 不僅是要編譯的 C# 方法,Burst 還能理解出 sin() 的三角函數(shù)屬性,同時知道 x 值較小時會出現(xiàn) sin(x) 等于 x 的情況,并了解它能替換為泰勒級數(shù)展開,以便犧牲特定精度。

跨平臺和架構(gòu)的浮點(diǎn)準(zhǔn)確性是 Burst 未來的目標(biāo)。

Entity Component System

Unity 一直以組件的概念為中心,例如:我們可以添加 Rigidbody 組件到游戲?qū)ο笊?,使對象能夠向下掉落。我們也可以添?Light 組件到游戲?qū)ο笊希顾梢园l(fā)射光線。我們添加 AudioEmitter 組件,可以使游戲?qū)ο蟀l(fā)出聲音。

我們實(shí)現(xiàn)組件系統(tǒng)的方法并沒有很好地演變。我們過去使用面向?qū)ο蟮乃季S編寫組件系統(tǒng)。組件和游戲?qū)ο蠖际恰按罅渴褂?C++ 代碼”的對象,創(chuàng)建或銷毀它們需要使用互斥鎖修改“id 到對象指針”的全局列表。

通過使用面向數(shù)據(jù)的思維方式,我們可以更好地處理這種情況。我們可以保留用戶眼中的優(yōu)良特性,即只需添加組件就可以實(shí)現(xiàn)功能,而同時通過新組件系統(tǒng)取得出色的性能和并行效果。

這個全新的組件系統(tǒng)就是實(shí)體組件系統(tǒng) ECS。簡單來說,如今我們對游戲?qū)ο筮M(jìn)行的操作可用于處理新系統(tǒng)的實(shí)體,組件仍稱作組件。那么區(qū)別是什么?區(qū)別在于數(shù)據(jù)布局。

class Orbit : MonoBehaviour{   public Transform _objectToOrbitAround;       void Update()   {       var currentPos = GetComponent<Transform>().position;       var targetPos = _objectToOrbitAround.position;       GetComponent<RigidBody>().velocity += SomehowSteerTowards(currentPos, targetPos)   }}

這種模式會被反復(fù)使用:組件必須找到相同游戲?qū)ο笊系囊粋€或多個組件,然后給它讀取或?qū)懭霐?shù)值。

這種方法存在很多問題:

  • Update() 方法被一個單獨(dú)的 Orbit 組件調(diào)用,下次調(diào)用 Update() 的會是完全不同的組件,它很可能造成代碼從緩存移出。

  • Update() 必須使用 GetComponent() 來找到 Rigidbody 組件。該組件也可以被緩存起來,但必須保證 Rigidbody 組件不被銷毀。

  • 我們要處理的其它組件位于內(nèi)存中完全不同的位置。

ECS 使用的數(shù)據(jù)布局會把這些情況看作一種非常常見的模式,并優(yōu)化內(nèi)存布局,使類似操作更加快捷。

傳統(tǒng)內(nèi)存布局

Unity中DOTS要實(shí)現(xiàn)的特點(diǎn)有哪些

Unity中DOTS要實(shí)現(xiàn)的特點(diǎn)有哪些

離散的數(shù)據(jù)導(dǎo)致搜索效率十分低下,還有 Cache Miss 的問題,這個問題可以參考:ECS的泛泛之談

ECS 數(shù)據(jù)布局

原型(Archetype)

ECS 會在內(nèi)存中對帶有相同組件(Component)集的所有實(shí)體(Entity)進(jìn)行組合。ECS 把這類組件集稱為原型(Archetype)。

下圖的原型就是由 Position 組件、Velocity 組件、Rigidbody 組件和 Renderer 組件組成的。

如果一個實(shí)體只有三個組件(不同于前面提到的原型),那么那三個組件就組成了一個新的原型。

下面的圖來自 Unite LA 的一次演講的講義, 很遺憾那次演講沒有錄制下來。講義可以在這里找到。

Unity中DOTS要實(shí)現(xiàn)的特點(diǎn)有哪些

ECS 以 16k 大小的塊(Chunk)來分配內(nèi)存,每個塊僅包含單個原型中所有實(shí)體組件數(shù)據(jù)。

Unity中DOTS要實(shí)現(xiàn)的特點(diǎn)有哪些

一個帖子中有人提供了更加形象的內(nèi)存布局圖,例如上半部分的原型由 Position 組件和 Rock 組件組成,其中整個原型占了一個塊(Chunk),兩個組件的數(shù)據(jù)分別存在兩個數(shù)組中,里面還帶著組件數(shù)據(jù)對應(yīng)的實(shí)體的信息。

Unity中DOTS要實(shí)現(xiàn)的特點(diǎn)有哪些

每個原型都有一個 Chunks 塊列表,用來保存原型的實(shí)體。我們會循環(huán)所有塊,并在每個塊中,對緊湊的內(nèi)存進(jìn)行線性循環(huán)處理,以讀取或?qū)懭虢M件數(shù)據(jù)。該線性循環(huán)會對每個實(shí)體運(yùn)行相同的代碼,同時為 Burst 創(chuàng)造向量化(Vectorization)處理的機(jī)會。

每個塊會被安排好內(nèi)存中的位置,以便于快速從內(nèi)存得到想要的數(shù)據(jù),詳情可以參考下面的文章。

Unity2018 ECS框架Entities源碼解析(二)組件與Chunk的內(nèi)存布局 - 大鵬的專欄 - CSDN博客

實(shí)體(Entity)

實(shí)體是什么?實(shí)體只是一個 32 位的整數(shù) key (和一些額外的數(shù)據(jù)例如 index 和 version 實(shí)體版本,不過在這里不重要),所以除了實(shí)體的組件數(shù)據(jù)外,不必為實(shí)體保存或分配太多內(nèi)存。實(shí)體可以實(shí)現(xiàn)游戲?qū)ο蟮乃泄δ埽踔粮喙δ?,因?yàn)閷?shí)體非常輕量。

實(shí)體的性能消耗很低,所以我們可以把實(shí)體用在不適合游戲?qū)ο蟮那闆r,例如:為粒子系統(tǒng)內(nèi)的每個單獨(dú)粒子使用一個實(shí)體。

實(shí)體本身不是對象,也不是一個容器,它的作用是把其組件的數(shù)據(jù)關(guān)聯(lián)到一起。

Unity中DOTS要實(shí)現(xiàn)的特點(diǎn)有哪些

系統(tǒng)(System)

Unity中DOTS要實(shí)現(xiàn)的特點(diǎn)有哪些

我們不必使用用戶的 Update 方法搜索組件,然后在運(yùn)行時對每個實(shí)例進(jìn)行操作,使用 ECS 時我們只需靜態(tài)地聲明:我想對同時附帶 Velocity 組件和 Rigidbody 組件的所有實(shí)體進(jìn)行操作。為了找到所有實(shí)體,我們只需找到所有符合特定“組件搜索查詢”的原型即可,而這個過程就是由系統(tǒng)(System)來完成的。

很多情況下,這個過程會分成多個 Job ,使處理ECS組件的代碼達(dá)到幾乎100%的核心利用率。ECS 會完成所有工作,我們只需要提供對每個實(shí)體運(yùn)行的代碼即可。我們也可以手動處理塊迭代過程(IJobChunk)。

當(dāng)我們從實(shí)體添加或移除組件時,ECS會切換原型。我們會把它從當(dāng)前塊移動到新原型的塊,然后交換之前塊的最后實(shí)體來“填補(bǔ)空缺”。

在 ECS 中,我們還要靜態(tài)聲明要對組件數(shù)據(jù)進(jìn)行什么處理,是 ReadOnly 只讀還是 ReadWrite 讀寫。通過確定僅對 Position 組件進(jìn)行讀取,ECS 可以更高效地調(diào)度 Job ,其它需要讀取 Position 組件的 Job 不必進(jìn)行等待。

這種數(shù)據(jù)布局也處理了 Unity 長期以來的困擾,即:加載時間和序列化的性能。為大型場景加載或流式處理 ECS 數(shù)據(jù)時,主要的操作是從硬盤加載和使用原始字節(jié)。

API 可用性和樣板代碼

對 ECS 的常見觀點(diǎn)是:ECS 需要編寫很多代碼。因此,實(shí)現(xiàn)想要的功能需要處理很多樣板代碼?,F(xiàn)在針對移除多數(shù)樣板代碼需求的大量改進(jìn)即將推出,這些改進(jìn)會使開發(fā)者更簡單地表達(dá)自己的目的。

我們暫時沒有實(shí)現(xiàn)太多這類改進(jìn),因?yàn)槲覀儸F(xiàn)在正專注于處理基礎(chǔ)性能,但我們知道:太多樣板代碼對 ECS 游戲代碼沒有好處,我們不能讓編寫 ECS 代碼比編寫 MonoBehaviour 更麻煩。

Project Tiny 已經(jīng)實(shí)現(xiàn)了部分改進(jìn),例如:基于 lambda 函數(shù)的迭代 API。

以上是“Unity中DOTS要實(shí)現(xiàn)的特點(diǎn)有哪些”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI