1.ECS現在只剩下純粹ECS(面向數據編程,數據在內存中更緊湊,更容易遍歷,Cache命中更高)(還提供動畫渲染等解決方案)

2.JobSystem多線程處理複雜計算

3.Burst編譯成最合適目標平台的code

三相之力

大佬們來分析分析實戰的優缺點

有哪些已經使用了DOTS技術的團隊也可以隨便說說


最近 DOTS 玩的比較多, 也來湊合幾句. 首先針對高贊回答中的一些言論正一下試聽:

DOTS 是不是專註解決性能問題?

是也不是, 準確來說, 能解決性能問題的技術是 JobSystem 和 Burst, ECS 只是專註於 Unity 應用長期以來缺乏的架構問題, 只不過 ECS 架構所特有的 Data-Orient 特性能更容易地解決 JobSystem 和 Burst 所需良好結構數據的問題.

能接近 Free Performance 的技術是 Burst, 唯一需要做的就是使用新的 Unity.Mathematics 來編寫程序, 當然目前也有不少的限制(比如必須要blittable數據, 不支持 Class等), 同時也會有許多莫名其妙的 Bug (我甚至遇到過運行時內存數據被污染), 因此建議在開發階段可以完全把 Burst 關掉.

Unity 目前在ECS上的動作是希望利用 ECS 的編譯器將 JobSystem 也變為 Free Performance, 比如下面的代碼就已經是 Jobify 過的代碼, 不過用戶可以幾乎無感:

public class MySystem : SystemBase
{
protected override void OnUpdate()
{
Dependency = Entities
.ForEach((Entity entity, int entityInQueryIndex, ref Data data) =&> {
// my logic
}).Schedule(Dependency);
}
}

對比一下沒有 Jobify 的寫法:

public class MySystem : SystemBase
{
protected override void OnUpdate()
{
Entities
.ForEach((Entity entity, int entityInQueryIndex, ref Data data) =&> {
...
}).Run();
}
}

與此同時, 使用 ECS 的另一個好處(或壞處)是, 你之前關於 Unity 專有的許多性能優化知識都沒用了, 什麼 Object Poolling, Instancing, Scene Streaming, Caching Components , GameObject.Find 等等都永遠說再見了.

耦合度變高了,原本實習生或者策劃能幹的活全堆給程序員了?

恰恰相反, 因為面向數據設計的初衷就是為了解耦合, 你見過誰在設計資料庫時出現"耦合度"問題嗎?實際上 ECS 的編碼過程就是設計數據的過程, 用好資料庫的第一第二範式就能設計出相對良好的程序結構, ECS world 里的數據也可以完全和副作用說拜拜.

至於策劃? 可以看看我翻譯的基於 Game Object Conversion 和 SubScene 的 DOTS 開發工作流 你就能明白為何結論也是恰恰相反: DOTS 讓兩者的角色更加清晰分離, 策劃你就別插手代碼了. 什麼? 你還是想改? Visual Scripting 了解一下.

TinyMode為啥和ECS綁定?

很簡單, 因為 H5 運行的環境恰恰是性能低下的 Mobile 平台, 性能這東西就像錢, 錢多就能買多點, 錢少買的少點, 並不是說我需要渲染上百萬個對象才需要性能, 而是到處都需要性能, 這也是為啥 Unity 的口號是 Performance By Default. 而不是 Performance By 高工資程序員.

C# 不如 C++ 高效?

毫無必要的爭論, 說這話的人知道 unsafe 關鍵字嗎? 事實上, 不是 Unity 不在業務層面用Cpp, 而是 Unity 壓根就不想用C++, 因此把整個引擎都逐漸轉向 C# 了, 原有 C++ 的 Runtime Core 會越來越小, 新出的 Package 都是 C# based 的, 為啥? 因為 C# 開發體驗好, Burst 和 JobSystem 又可以直接榨乾 CPU , 兩全其美.

DOTS學起來太難了!

不不不, 一點都不難, 之所以你覺得難, 是因為你沒有轉變過來 Data-Orient Design 的思想, 滿腦子都還是繼承一個 Person 類得到 Woman類, 這完全是兩種處理問題的思路, 腦子轉不過彎來當然覺得難了.

既然 DOTS 這麼牛逼, 為啥還沒普及?

很簡單, DOTS 現階段在完成度上就是接近廢品的殘次品, 我在這裡說到過現階段的DOTS, 粘過來給你們看看.

非常遺憾, 經過了 Unity 長達近三年的宣傳, DOTS到今天完成度依然極低, 基本上處於不可用的狀態, 一款遊戲引擎必不可少的部分可簡單列為下面八個, 接下來我分開說說在 DOTS 技術棧里他們的現狀

  • Physics - 3分 物理自然是大部分遊戲都不可或缺的部分, DOTS配套的 Unity Physics 依然處於非常早期的狀態, Authoring 工具幾乎為零, 甚至 Joint 或者 CharacterConroller 都要從 Sample 里找代碼自己挨個兒實現, 由於它提供了一套船新的 API, 開發的過程只能用極難使用來形容, 來看看Trigger 事件的代碼就知道了, 而在使用過程中也是Bug層出, 經常會遇到類似 Mesh Collider Bake錯誤, 而與之同期宣傳的 Havok Physics for Unity 更是奇妙, 在長達半年的時間裡, 只是單純引入 Havok 的便會導致 HDRP 的渲染出問題. 而更進階的布料, 粒子, 地形等也處於幾乎為零的狀態.
  • Graphics - 3分 DOTS與之配套的是圖形系統是 HybridRenderer, 不過目前也只支持 Mesh Renderer, 什麼 Particle 啦, Trail 啦, VFX都還沒影兒, 更可惜的是官方現在優先關注於 HDRP 的兼容性(雖然也不怎麼樣), URP或者Built-In Pipeline就基本別想了, 遇到了問題也只能雙手一攤.
  • Audio - 0分 Unity目前只開發了底層的 DSP 系統, 上層的 DOTS audio 說了一兩年了連影子都沒有, 完成度接近於零.
  • Animation - 0分 Unity Animation 依然處於極早期的開發中, DOTS Sample 展示了一些最最基本的使用, 類似 Animator 這樣的高層方案現在還沒影兒.
  • Network - 2分 也是 Unity 吹了兩年的東西, DOTS Sample 中用的 NetCode 只不過把 FPSSample 中的部分代碼挪過去了, 目前版本號 0.0.4, 意思就是太早期了別用.
  • UI - 0分 IMGUI, UI Element 和 Unity UI 三個Unity的產品均不支持 DOTS
  • AI - 0分 navmesh, ml-agent, ai planner 三個Unity的產品均不支持 DOTS
  • Input - 2分 老的 Input System 相對簡單與DOTS沒啥關係, 可新的 Input System 居然也不支持 DOTS, 幸運的是 InputSystem 相對獨立, 嫁接進DOTS並不困難.

所以, 要怪就怪 Unity 的營銷部門, 吹太過了, 步子太大扯到了蛋, 整個社區里瀰漫著這樣的氣氛:

DOTS Sample 不是看起來挺好的啊

是的, 看起來是挺好的, 總的來講 DOTS 肯定是未來, 現在是算是學習 DOTS 的不錯時間, 最近正在寫一個關於 DOTS Sample 的系列文章, 我會盡我所能將 DOTS Sample 所折射的 DOTS 的現階段的優缺點和開發過程都理出來, 有興趣的同學可以關注一下.

林七千:深入了解 Unity DOTS Sample (序章)?

zhuanlan.zhihu.com圖標


DOTS里,除了ECS我都挺喜歡的(

不知道是不是沒理解全面,僅從官方安利來看,我總覺得ECS把代碼的耦合度變高了,維護和調試體驗很差,原本實習生或者策劃能幹的活全堆給程序員了。

可能在我的理解中,ECS只需要用來寫遊戲中特別耗費性能的部分就好了,其他情況下還用原來的那一套就挺好的,把遊戲全局甚至UI都強行上ECS簡直有點災難。

從最近的更新看來,unity確實可以這麼幹了,可以在gameobject的項目里插入部分ECS的東西,不過感覺兩者之間的互通體驗還有待提高。

最後,比較不理解的是為什麼要把TinyMode和ECS強行綁定。講真,我都tm上h5了,你覺得我還在乎性能嗎?ecs在瀏覽器里又能提升多少性能呢。反而本來在egret上四五千塊錢一個月的程序能幹的活,在tiny上的工資成本直線上升


吐槽之外,對於DOTS技術本身,它很厲害。

Job System和Burst我們項目中有用上了,效果顯著。

更重要的是,DOTS擴寬了Unity的可能性,你可以用它做一些簡單的小東西,甚至遊戲行業之外的東西,也可以用它做很多以前它難以勝任的大作。

對於Unity來說是好事。


其實我個人還是挺喜歡他們這種拚命壓榨C#性能也不在業務層面用Cpp的折騰精神的。

並且挺願意參與其中的。


DOTS是Unity因為宣傳需要起的一個名字。它所包含的這三種技術中,與開發者關係最密切的是其中的ECS模式。這一技術應該會在這兩年受到很大關注,也會有很快的發展。

先說一說我的觀點,一些看法會隨著DOTS的發展而修正。

1、Unity對多線程的支持正在改善,但傳統的腳本寫法是很大的障礙

經測試,Unity的JobSystem已經在2018之後的版本中默認啟用,大部分引擎內部的功能已經可以被多核CPU並行執行,觀察CPU佔用率效果很明顯。

但是腳本執行對時序要求高,如果不使用ECS,還用傳統寫法,腳本運行無法並行化。

針對ECS的性能,咱們的專欄《遊戲開發入門指南——Unity+》中的一篇文章已經做了簡單測試:(鏈接:ProcessCA:Unity 實體組件系統(ECS)——性能測試)

測試採用一個吃豆小遊戲,分別寫了MonoBehaviour和ECS兩種版本

在敵人數量在17000左右時,遊戲幀數掉到了30幀。

首先來看一下基於Monobehavior的實現。由於沒有使用Unity的物理系統,所以基本上是腳本跟渲染兩大塊佔用CPU的性能。

在主線程中一幀的時間已經超過30毫秒了,其中腳本執行就佔用了幾乎27毫秒。

我們可以看到Job System上的線程也幫我們分擔了不少渲染上的負擔:

值得一提的是在unity2017之後加入了Job System,所以Unity的渲染也會被分配到不同的線程中去執行,在一定程度上提高了整體運行效率。(也就是說,即便使用老的腳本系統,也能享受到JobSystem的一部分好處)

在使用了ECS化的腳本以後,並行化的腳本分配到了不同的工作線程上,充分利用了多核的性能。

在不同Worker Thread中有藍色的代碼執行片段

最後測試了ECS系統的極限,在敵人數量超過65000個時幀數下降到30幀。幾乎是Mono的4倍(可能是因為測試機處理器有4個核心)。

2、DOTS是什麼?

DOTS是給幾項優化技術打包起了個名字,主要還是為了宣傳考慮。

它主要包含3個技術:

  1. ECS模式。這種編程模式,要求遊戲邏輯和引擎功能,都要按照 實體+組件+系統 的框架構建。採用ECS模式後,可以得到性能與多線程並行的優化。
  2. JobSystem。一套先進的多線程任務管理系統,搭配ECS時可以獲得最佳性能。不搭配ECS也能在引擎的基本功能方面享受到好處。
  3. Burst,可以代替.Net原來的運行時環境(Mono或IL2CPP),用全新的編譯器編譯為原生代碼,得到比IL2CPP更快的執行速度。當然代碼最好能配合Burst做一些代碼上的改動,以得到充分優化、避免BUG :)

DOTS系統成熟以後,遊戲的腳本邏輯部分會和現有的組件式系統完全不同。原有腳本寫法和ECS寫法的區別,比兩種語言的差別要大得多。所以起個好名字逐步發展新體系也是必要的。

3、ECS是什麼?

首先ECS並非一種全新的技術,也不是Unity首先提出的。

這種技術的出現非常早,而近幾年突然火爆,是因為暴雪的《守望先鋒》。《守望先鋒》的伺服器和客戶端框架完全基於ECS構建,在遊戲機制、網路、渲染方面都有非常出色的表現,這種技術上的巨大成功,必然會引領一波ECS的潮流。

這篇GDC分享3年前火了一陣

ECS不屬於某種「設計模式」,之前我們常說的「設計模式」是在面向對象的框架之下討論的,而ECS完全是另一種面向對象的編程方法,它對傳統編程方法的顛覆性比Actor模式更強。至少目前來看,ECS僅適合於遊戲開發領域。

  1. E代表實體(Entity)。實體在邏輯意義上是組件的容器,每個實體有若干個組件;但在實現上它只需要一個整數ID,區分不同的實體。
  2. C代表組件(Component),它不是目前Unity中的組件,而是一種只擁有數據,不能具有任何方法的結構體。
  3. S代表系統(System),和C相反,系統只能具有方法而不具有任何數據。

系統是程序邏輯的載體,遊戲中有很多System會反覆執行。每個System執行時它不管什麼Entity,而只做兩個步驟:

  1. 從所有的組件中篩選合適的「組件組合」。例如某個移動system,只關心同時具有「移動組件」和「旋轉組件」的對象。
  2. 對篩選出的組件進行某種操作。例如上面的移動system,會修改移動組件中保存的位置變數,再修改旋轉組件中保存的朝向變數。

一個遊戲中,大量的Component每一幀被很多System修改,就形成了遊戲邏輯。

那為什麼說ECS非常厲害呢?關鍵是:

  1. 每個system所做的事情都非常簡單,非常容易並行執行。而且只要標記出哪些數據是只讀的、哪些是可寫的,就能讓ECS系統進一步優化。現代手機和PC核心數都挺多,可以達到恐怖的並行效率。
  2. 由於組件C只是單純的數據,大量組件數據在內存中很容易按順序排好位置,系統處理這些整整齊齊的數據極為高效。而且,這種內存布局可以提高CPU緩存命中率,現代CPU動輒有十幾兆、幾十兆的緩存,在緩存中執行比火箭還快。

4、ECS目前的發展阻力是什麼?

通過前面的介紹可以看出,ECS可不是什麼「易學易用」的新技術,它對現有的開發模式,甚至現有的Unity編輯器來說都是顛覆性的。

不僅如此,ECS對初學者來說有著巨大障礙:除了遊戲技術圈,人們很難在任何其它地方學到ECS編程方法

無論你在什麼地方學習編程,能搞清楚傳統的面向對象已經很不錯了,ECS只能在你有一定編程基礎後,通過網路資料實踐自學。這一問題在ECS的推廣方面是致命的。

因為ECS這一技術本身還是比較新穎的,高手們都還沒有把ECS系統摸透,沒有系統化,更別提能夠在不誤人子弟的前提下教會新人。

但是話說回來,ECS雖然是一種全新的編程思想,但對於一些專業開發者來說並不難,比如咱們專欄里已經有寫過一些實踐分享:

ProcessCA:Unity 實體組件系統(ECS)——性能測試

zhazha chen:Unity ECS 高性能探索

對於專業開發者來說,目前在Unity中實踐ECS,阻力來自於:Unity官方對於DOTS依然是緩慢開發中,還遠遠談不上成熟

例如,由於ECS的遊戲對象不再兼容原來的GameObject,導致ECS物體在場景編輯器中看不到。如何解決官方給出了至少兩種方案:加個代理適配,或是徹底換成新編輯器窗口(比如Entity Debugger之類的工具)。但是這些方案都還不完善…… :)

還有ECS在渲染方便和原系統存在兼容問題,雨松Momo的ECS分享視頻中,用了較多篇幅解釋在DOTS技術棧下如何妥善渲染。可以看出問題還很多,針對不同項目要考慮不同的方法。

5、我們應該如何看待DOTS?

重點是,雖然Unity的DOTS技術存在巨多問題,無論Unity ECS還是Burst,說起來都是各種吐槽。但作為專業的軟體工程師,應當以最大程度的積極性擁抱這些新技術

縱觀歷史,新技術出現時總是帶著一身毛病——還沒馬車跑得快的汽車,還沒蒸汽機好用的內燃機,還沒滑膛槍可靠的線膛槍,感覺隨時會核爆的核電站。一切當今習以為常的東西,在當年都被無情地嘲笑過。

就算髮展慢、就算髮展中出現下滑和反覆。ECS技術在內存布局、緩存友好性、多線程友好性等方面達到了傳統編程模式望塵莫及的高度。甚至可以說,很常規的ECS代碼就已經能達到某些深度優化過的傳統代碼的性能。

所以從發展趨勢看,我們沒有理由故步自封、拒絕這一新技術。

PS:話說回來,現在學DOTS並不是現在就要用到商業項目里。在商業項目中,採用這種尚不成熟的新技術一定要得到團隊的一致同意和Leader的支持,否則就是非常不負責任的決定。 :)


瀉藥,個人觀點有3條:

  1. ECS是好東西,Unity的ECS不是。
  2. Job System是好東西,Unity的Job System也不錯,但還有一定的進步空間。
  3. Burst目前看來提升確實很大,但是沒有跟手寫SIMD比較過。

ECS的思想其實從十幾年前機能嚴重不足的時候,到現在都一直在影響遊戲開發行業,無論是裡邊有過程式編程的影子,還是函數式編程的影子,亦或是針對緩存和CPU密集運算的優化思想,都是在切實的解決遊戲邏輯和渲染中的痛點,而且即使不是純ECS,許多團隊的框架也在向這個方向靠攏。Unity的ECS一方面完成度太低,之前看到版本號還是0.x,就比較尷尬,另外從我個人自私的角度分析(本人是個寫渲染的)就是沒大有和渲染層接壤,比如VLK和DX12這種現代的API是可以直接並行產生PSO,並行準備CommandBuffer(CommandList) 等等,在這些方面Unity只提供了SRP這種很上層的封裝,很難將Job System和ECS靈活的應用上去。

Job System這種線程調度方式,應該是同步多線程中速度最快的方法,因為隊列數量提前可知所以幾乎不存在高消耗的鎖和等待,同時一次性觸發不會涉及到上下文,線程啟動消耗等。但是官方的Demo好像有涉及到一些非同步多線程,個人認為Job System這種一次性觸發的設計模式並不適合非同步多線程,平時我在開發時也經常會用到Job System這種多線程思想,對性能的提升大有裨益。

之前有人問Job System,Unreal的Task Graph有什麼關係和區別,看起來Task Graph在設計時更多的考慮了非同步能力,而不是這麼注重一口氣憋住的同步能力,而且Unreal官方似乎推薦使用Task作為延時的邏輯觸發,而Unity推薦使用協程做等待,讓其他線程做純運算,在設計理念上應該存在一些不同,本人在此不置褒貶。

Burst據說是處理SIMD的效果很好,經過一些測試發現效果也確實不錯,但是平常這種優化手寫也是能做到的,所以究竟和平常手寫的帶_m128這種的數學庫,比如DX提供的DirectX::XMVECTOR DirectX::XMMATRIX有多少提升,這一點還有待探索,這裡持保留態度。


最近用了下坑太多了。這東西目前給我感覺就是宣傳能100%解決問題A,實際上能輕鬆解決10%的問題A,深入進去解決40%,開洞解決80%。但是因為宣傳給的例子全選的都是在那10%到40%的情況,讓你以為可以輕鬆解決100%的問題。

1. 一個問題是大量實現是基於非語法層面的命名做的trick,考慮到c#和unity一貫如此倒也能接受,但是很多trick不放文檔上跟你講清楚,就很坑。舉個例子,自己寫個NativeContainer,一定要有一個AtomicSafetyHandle和一個DisposeSentinel。然而你光有這兩個field還不行,必須強制命名為m_Safety和m_DisposeSentinel。NativeContainerAttribute的doc里並沒有講這一點,翻了forum才發現這個問題。

2. 大量功能性api不完全/只能在主線程跑,你想job化都不行。舉個例子,VisibleLight這個struct的設計,顏色位置啥的信息都有,就是沒有陰影信息,所以必須得調VisibleLight.light去拿原來那個light,然而這是一個managed call,所以Job里用不了,你說dt不dt。

3. 接1,各種奇怪的判定方法。IJobParallelFor里會把任意兩個type一致的打了[NativeDisableParallelForRestriction]的NativeContainer認為是兩個可能會混淆的NativeArray。且不說這兩個東西也不一定是NativeArray,哪怕這兩個完全內容不一樣它也不管。我就納悶,我都取消並行的檢查限制了,全都自己管理了,這兩NativeContainer會不會混淆我不比你unity更有b數?

所以說這玩意兒就是為「理想問題」而設的。寫個什麼千萬個小行星繞太陽轉沒問題,可我要實現這玩意幹啥?


推薦閱讀:
相关文章