本文轉(zhuǎn)自Unity Connect博主?EntherVarope
ECS是什么?可以做什么理疙?為什么ECS成為我的選擇?
什么是ECS(實(shí)體組件系統(tǒng))?
其實(shí)ECS的概念早就誕生了悯舟,但是它是因?yàn)槭赝蠕h才逐漸被人所知。(暴雪的守望先鋒是基于ECS模式設(shè)計(jì)的砸民,用于改善在大場(chǎng)景下多角色運(yùn)算的效率)
ECS是一種軟件架構(gòu)模式抵怎,由三個(gè)元素組成:實(shí)體(Entity),組件(Component)和系統(tǒng)(System)(看起來和MVC很相似)岭参。游戲程序分為這三個(gè)主要元素反惕,并且通過定義每個(gè)系統(tǒng)的責(zé)任和關(guān)系來管理游戲。
實(shí)體代表游戲世界中的事物演侯。實(shí)體本身沒有特定功能姿染,它們將會(huì)被組件填充來成為一個(gè)實(shí)體。
組件是附加到事物的數(shù)據(jù)秒际。重點(diǎn)不是對(duì)象悬赏,而是數(shù)據(jù)狡汉,沒有辦法操縱它。比如操作游戲的角色時(shí)闽颇,位置盾戴,速度和體力等每個(gè)狀態(tài)都將成為一個(gè)組成部分,并與稱為“角色”實(shí)體相關(guān)聯(lián)兵多。另外尖啡,實(shí)體中的字段信息也被表示為組件部分。
系統(tǒng)是游戲世界的法則中鼠。給定與實(shí)體關(guān)聯(lián)的某些組件作為數(shù)據(jù)輸入源可婶,或者更新某些組件的值(可能與輸入的組件相同)。隨著整個(gè)系統(tǒng)更新每一幀援雇,游戲世界也在不斷進(jìn)行矛渴。我認(rèn)為最容易想象的是物理定律。例如惫搏,想象一個(gè)剛體實(shí)體具温。它的運(yùn)動(dòng)基于利用位置和速度兩個(gè)分量的系統(tǒng)來更新坐標(biāo)。
粗略地說筐赔,系統(tǒng)負(fù)責(zé)處理铣猩,組件負(fù)責(zé)數(shù)據(jù),而實(shí)體是一組組的組件茴丰,用于過濾系統(tǒng)正在處理的內(nèi)容达皿。由于系統(tǒng)和數(shù)據(jù)是完全分開的,因此它與面向?qū)ο蟛患嫒荨?/p>
Unity也具有類似于ECS的架構(gòu)(自Unity2018.2起已提供ECS)贿肩。實(shí)體是已經(jīng)削減到極限的游戲?qū)ο蠊δ苈鸵M件是組件的序列化數(shù)據(jù)部分,其余部分是系統(tǒng)汰规。Unity ECS是對(duì)純ECS的改進(jìn)汤功。
ECS可以做什么?
UnityECS正在圍繞這一設(shè)計(jì)模式開發(fā)新的“面向數(shù)據(jù)技術(shù)堆椓锵”(DOTS),如果經(jīng)常關(guān)注Unity的發(fā)布會(huì)滔金,就或多或少對(duì)它有所了解。但是事先寫在前邊茂嗓,ECS并不能加速任何類型的游戲/程序餐茵。
ECS有望加速游戲的類型
彈幕游戲
有海量單位的大型RTS
開放世界/沙盒
集群模擬(例如新出的“動(dòng)物星球”)
此類具有大量“遵循同一運(yùn)算規(guī)則”對(duì)象的程序,便可以利用ECS來加速述吸。
為什么ECS成為我的選擇忿族?
答案很簡(jiǎn)單:因?yàn)樗值目欤⌒史浅V撸绕饌鹘y(tǒng)的面向?qū)ο缶幊坛澹鼘?duì)內(nèi)存的利用率是成幾何倍數(shù)的增長(zhǎng),還有一些其他的優(yōu)點(diǎn):
輕松并行化--------清晰的系統(tǒng)輸入輸出和小粒度
良好的緩存效率------通過順序訪問組件數(shù)組獲得空間局部性(動(dòng)態(tài)場(chǎng)景讀取十分方便)
耦合度低---------因?yàn)橄到y(tǒng)僅交換數(shù)據(jù)
易于測(cè)試---------因?yàn)橄到y(tǒng)沒有狀態(tài)
當(dāng)然ECS也有它自身的一些缺點(diǎn):
對(duì)于習(xí)慣了面向?qū)ο缶幊痰娜似佣粒枰淖兙幊趟季S模式
在Unity推出完善的工具集前屹徘,管理內(nèi)存是一件困難的事
至于為什么ECS如此之快,內(nèi)容比較多衅金,感興趣的可以繼續(xù)往下看噪伊,跳過也無妨。
在傳統(tǒng)的流程中氮唯,我們制作物體鉴吹,為其添加腳本。這種做法有一些固有的缺點(diǎn)和性能缺陷惩琉。首先豆励,數(shù)據(jù)和處理它們的方法是緊密耦合的,代碼重用率較低瞒渠。此外良蒸,系統(tǒng)非常依賴引用類型,引用錯(cuò)誤屢見不鮮伍玖。
最重要的是嫩痰,在上圖的示例中:Gun與Player所引用的Transform、Rigidbody窍箍、Collider等這些關(guān)鍵腳本被分散在堆內(nèi)存中串纺,數(shù)據(jù)將不會(huì)轉(zhuǎn)換成可由更快的SIMD(單指令多數(shù)據(jù)流)矢量單元進(jìn)行操作的形態(tài)。
上圖顯示了這種數(shù)據(jù)存儲(chǔ)方法的隨機(jī)偶發(fā)性質(zhì)纺棺。每一個(gè)單引用,在使用時(shí)都有可能會(huì)將其所有的成員變量從系統(tǒng)內(nèi)存中全部拉出晰搀,舉個(gè)例子五辽,當(dāng)我命令Gun進(jìn)行開火,子彈飛出外恕,對(duì)子彈的坐標(biāo)進(jìn)行運(yùn)算讓它飛行杆逗,表面上看起來僅僅是對(duì)子彈這個(gè)對(duì)象的Transform中的position進(jìn)行了操作,但實(shí)際上鳞疲,子彈的rotation罪郊,gameobject屬性,還有等等等等其他成員也一并拉出來操作了尚洽。
綠色塊表示開發(fā)想象中認(rèn)為操作引用的成員,而實(shí)際上,硬件聽從腳本的命令從內(nèi)存中獲取數(shù)據(jù)時(shí)癣疟,緩存中會(huì)填充許多無用的數(shù)據(jù)(紅色的塊)挣柬,如果將為要移動(dòng)的GameObject設(shè)置成一個(gè)獨(dú)立的只有位置與旋轉(zhuǎn)成員的矩陣,那么系統(tǒng)就能夠在很短的時(shí)間內(nèi)執(zhí)行操作睛挚。
在ECS中困后,整個(gè)的流程將會(huì)是:
只需要考慮每一種GameObject所包含的數(shù)據(jù)實(shí)體则酝,而不用考慮自己的組件集合(拋棄了Transfrom疏橄,Rigidbody等)伟叛,將處理與各個(gè)對(duì)象類型完全分離。實(shí)體僅僅是一個(gè)句柄(或者說是一個(gè)標(biāo)識(shí)符)永遠(yuǎn)索引它表示的不同數(shù)據(jù)類型的集合(ComponentDataGroups)系統(tǒng)可以通過這些句柄來對(duì)所有組件進(jìn)行過濾和操作淤击,而不需要將系統(tǒng)與實(shí)體類型明確結(jié)合匠抗。這種工作機(jī)制有很大優(yōu)勢(shì),它不僅能提高緩存效率污抬,縮短訪問時(shí)間汞贸,它還支持現(xiàn)代CPU中的使用數(shù)據(jù)對(duì)齊的先進(jìn)技術(shù)(自動(dòng)矢量化 即:SIMD),這種技術(shù)帶來的效率提升是極為可觀的壕吹。在Unity的DOTS中著蛙,還可以使用Brust Complier跳過中間語言的編譯,使得性能進(jìn)一步的提升耳贬。
關(guān)于數(shù)據(jù)對(duì)齊與SIMD
實(shí)際編程中踏堡,對(duì)內(nèi)存的管理總是不可能達(dá)到完美利用,總會(huì)有或多或少的內(nèi)存塊處于閑置狀態(tài)咒劲,閑置內(nèi)存不僅沒有任何用處顷蟆,系統(tǒng)仍然要訪問它們?cè)龃笤L問開銷(浪費(fèi)時(shí)間)。傳統(tǒng)的面向?qū)ο笙码m然降低抽象門檻了腐魂,但是對(duì)內(nèi)存來說實(shí)際上是很不友好的(構(gòu)建對(duì)象時(shí)帐偎,數(shù)據(jù)引用總是雜亂無序的)。面向數(shù)據(jù)編程將數(shù)據(jù)提取出來蛔屹,不用關(guān)心他們實(shí)際上的聯(lián)系削樊,僅僅是由實(shí)體這個(gè)索引來引用,這就代表兔毒,對(duì)于同類型的數(shù)據(jù)漫贞,可以將他們放在同一個(gè)內(nèi)存塊里,無論是對(duì)內(nèi)存的利用或者是系統(tǒng)的訪問都是十分便利的育叁。
(PS:就算是數(shù)據(jù)對(duì)齊了迅脐,也不能徹底消除閑置內(nèi)存,但是比起傳統(tǒng)的內(nèi)存結(jié)構(gòu)來說豪嗽,閑置內(nèi)存的數(shù)量會(huì)大為減星疵铩)豌骏。
SMID:
SIMD全稱Single Instruction Multiple Data,單指令多數(shù)據(jù)流隐锭,能夠復(fù)制多個(gè)操作數(shù)窃躲,并把它們打包在寄存器的一組指令集。
傳統(tǒng)的CPU使用SISD來完成邏輯運(yùn)算钦睡,過程可以籠統(tǒng)的概括為一個(gè)執(zhí)行單元先訪問內(nèi)存框舔,根據(jù)命令找到第一個(gè)操作數(shù),再一次訪問內(nèi)存赎婚,找到第二個(gè)操作數(shù),才能根據(jù)命令進(jìn)行邏輯運(yùn)算(在查找操作數(shù)的過程中樱溉,由于數(shù)據(jù)的引用是雜亂無序的挣输,執(zhí)行單元只能遍歷每一個(gè)內(nèi)存塊,這就造成了性能瓶頸)福贞。
新世代的CPU所采用的SIMD則是由數(shù)個(gè)執(zhí)行單元同時(shí)訪問內(nèi)存撩嚼,一次性找到所需的操作數(shù)進(jìn)行運(yùn)算,事實(shí)上挖帘,由于進(jìn)行了數(shù)據(jù)對(duì)齊完丽,每一個(gè)執(zhí)行單元查找內(nèi)存的效率比SISD的查找要高出不少,這就好比將無數(shù)的快遞按照地區(qū)分門別類拇舀,一旦CPU發(fā)出“拿出上海地區(qū)的快遞“指令逻族,每一個(gè)快遞員(執(zhí)行單元)都會(huì)直奔存放上海快遞的貨架而不用一個(gè)貨架一個(gè)貨架的搜索骄崩。這樣的特性非常適合大數(shù)據(jù)運(yùn)算聘鳞。
以上內(nèi)容都是關(guān)于ECS本身的優(yōu)勢(shì),接下來將闡述Unity基于ECS進(jìn)一步開發(fā)的面向數(shù)據(jù)技術(shù)棧工具DOTS要拂。
UnityDOTS(Data-Oriented Technology Stack)
Burst Complier
爆發(fā)式編譯器(抠璃?)
爆發(fā)編譯器是UnityECS為了更高效地組織數(shù)據(jù)所產(chǎn)生的后臺(tái)性能增益開發(fā)的。從本質(zhì)上講脱惰,突發(fā)編譯器將根據(jù)玩家設(shè)備上的處理器功能優(yōu)化代碼操作搏嗡。例如,您可以通過填充未使用的寄存器來執(zhí)行 16拉一、32 或 64次浮點(diǎn)預(yù)算采盒,而不是一次只進(jìn)行 1 次浮點(diǎn)運(yùn)算。
新的編譯器技術(shù)基于 Unity 的新數(shù)學(xué)命名空間(Unity.mathematics)和 C# 作業(yè)系統(tǒng)(JobSystem)以及改進(jìn)過的高性能C#(HPC)舅踪,基于系統(tǒng)知道數(shù)據(jù)已經(jīng)通過實(shí)體組件系統(tǒng)正確設(shè)置的事實(shí)纽甘。英特爾 CPU 的當(dāng)前版本支持英特爾? SIMD 流指令擴(kuò)展 4(英特爾? SSE4)、英特爾? 高級(jí)矢量擴(kuò)展指令集 2(英特爾? AVX2)以及用于浮點(diǎn)和整數(shù)的英特爾? 高級(jí)矢量擴(kuò)展指令集 512(英特爾? AVX-512),AMD的支持3D Now的CPU等都是能支持爆發(fā)式編譯器的(除非是在十分老舊的電腦上運(yùn)行抽碌,否則不需要考慮兼容性問題悍赢,因?yàn)樽詣?dòng)矢量化技術(shù)已經(jīng)成為現(xiàn)在與未來的CPU主流標(biāo)準(zhǔn))决瞳。該系統(tǒng)還支持在每種方法中使用不同的精確度,以過渡方式應(yīng)用左权。例如皮胡,如果您在低精度的頂級(jí)方法內(nèi)使用余弦函數(shù),則整個(gè)方法也將使用余弦的低精度版本赏迟。該系統(tǒng)還根據(jù)當(dāng)前運(yùn)行游戲的處理器的功能支持屡贺,通過動(dòng)態(tài)選擇適當(dāng)?shù)膬?yōu)化功能為 AOT(前期)編譯做準(zhǔn)備。
這種編譯器的另一個(gè)優(yōu)勢(shì)是確保游戲的未來適用性锌杀。如果一款全新的處理器產(chǎn)品線上市甩栈,其中包含一些令人驚嘆的新功能,Unity 可以在后臺(tái)為您完成所有費(fèi)力工作糕再。只需對(duì)編譯器進(jìn)行升級(jí)量没,以獲取優(yōu)勢(shì)。編譯器是基于軟件包的突想,無需 Unity 編輯器更新即可升級(jí)殴蹄。該爆發(fā)式編譯器軟件包將以自己的節(jié)奏進(jìn)行更新,因此您將能夠利用最新的硬件架構(gòu)改進(jìn)和功能猾担,而無需等待代碼升級(jí)到下一個(gè)編輯器版本袭灯。
C#JobSystem
C#作業(yè)系統(tǒng)
大多數(shù)使用多線程代碼和通用任務(wù)系統(tǒng)的人都知道編寫線程安全代碼很難。線程爭(zhēng)用情況雖然很罕見绑嘹,但仍然可能會(huì)發(fā)生稽荧。如果編程員沒有想到這個(gè)問題,可能會(huì)導(dǎo)致潛在的程序嚴(yán)重錯(cuò)誤工腋。除此之外蛤克,上下文切換的成本,Debug的成本很高夷蚊,因此學(xué)習(xí)如何平衡工作負(fù)載以盡可能高效地跨核心運(yùn)行是很困難的构挤。最后,編寫 SIMD 優(yōu)化代碼或 SIMD 內(nèi)聯(lián)函數(shù)是一種深?yuàn)W的技能惕鼓,有時(shí)最好交給編譯器去完成筋现。新的 Unity C# 作業(yè)系統(tǒng)為您解決所有這些難題,以便您可以在現(xiàn)代 CPU 中放心地使用所有可用的內(nèi)核和 SIMD 矢量化箱歧。
總而言之矾飞,C#的JobSystem提供一系列的多線程解決方案,讓編寫多線程程序更為安全方便呀邢。
常規(guī)的Unity如果要開發(fā)多線程洒沦,不僅要引入外部的實(shí)現(xiàn)方式,Debug過程也是繁瑣不可視的价淌,而引入C#JobSystem申眼,可以讓系統(tǒng)智能化管理安排多線程任務(wù)瞒津,使用Unity自身封裝的多線程安全集合(例如:NativeArray<>)可以有效防止線程沖突的問題。
在接下來的文章中括尸,我會(huì)告訴你如何利用DOTS來編寫一個(gè)ECS程序巷蚪。
原文鏈接:https://connect.unity.com/p/unityecs-yi?app=true
戳上方鏈接下載官方app即可提前了解接下來的文章,還有技術(shù)社區(qū)在線答疑濒翻,更多學(xué)習(xí)資源等你來發(fā)現(xiàn)