這篇文章是要和大家分享 JobSystem 裡面較為深入的細節,主要是一些前兩篇文章中快速帶過的概念,不在前面的文章提出來是不想讓篇幅暴增,也是因為這樣所以這篇文章顯得比較雜亂一點,大家就把它當成 JobSystem 的小百科來看好了。
大綱
在介紹 Job 類型時提到:
一個 job struct 內包含 blittable 類型與 NativeContainer 的成員變數
這邊來說明一下他們分別是什麼,以及為什麼只有他們。
在 .NET 架構中,有一些類型在 managed 與 unmanaged 程式碼之間可以直接傳遞使用,不需要進行任何轉換,這些類型就稱之為 Blittable types,中文似乎可以稱之為「可直接複製(到本機結構)的類型」。
相信大家應該都會有疑問,Blittable types 跟我們常聽到的「實質型別」會是一樣的東西嘛?
這邊我們要先理解一下 .NET 與 C# 的關係:
有了這個概念之後,我們先來看 C# 「實質型別」與「參考型別」:
再來看 .NET 的 Blittable types 與 Non-Blittable type,依照 官方文件 的說明,分類如下:
由以上比較簡潔的描述可以得知 Blittable types 和實質型別其實是不同的概念,因此大家在使用 Job 時需要特別留意。
關於 NativeContainer,系列的第一篇文章有簡單介紹到:
A thread-safe C# wrapper for native memory,它是一個能讓不同的執行緒之間共享的記憶體空間。我們能夠把主執行緒的資料透過 NativeContainer 提供給 job 進行讀寫。
除此之外,NativeContainer 是唯一可以讓我們將 job 的結果拿來給主執行緒共享的媒介。
Unity.Collections
裡面包含了兩 Unity 內建的 NativeContainer 物件:
在 Collections package 裡面還包含了額外類型的 NativeContainer,詳細種類有哪些可以參考 Collections 的 說明文件。
預設情況下,job 同時擁有對 NativeContainer 讀取與寫入的權限,然而這會在某些情況下對效能造成負面影響,這是因為當有一個 job 正在寫入某個 NativeContainer 時,job system 不會允許你 schedule 另一個對 NativeContainer 有寫入權限的 job 開始執行。
看起來有點難理解,我們舉個例子,當你有一個 jobA 正在執行,jobA 正在對某個 NativeContainer 進行寫入,這時候你安排了一個 jobB 預期它能跟 jobA 同時執行,然而 jobB 也擁有對 NativeContainer 寫入的權限,儘管 jobB 只需要讀取它,這種情況下,job system 只會等 jobA 執行完畢後才開始執行 jobB,因為要避免 jobA 與 jobB 同時對 NativeContainer 進行寫入,但這根本不會發生。
因此,我們在宣告 NativeContainer 時最好為他們標上 [ReadOnly]
或 [WriteOnly]
的標籤。
[ReadOnly] public NativeArray<int> input;
如系列的第一篇文章所述:
NativeContainer 的記憶體空間配置與釋放有三種 Allocator 類型,在建立 NativeContainer 時,必須要將它指定為其中一種:
* Allocator.Temp:最快的配置類型,存在時間小於等於一幀。
* Allocator.TempJob:速度適中的配置類型,存在時間為四幀,需要在存續時間內主動呼叫Dispose()
來釋放,否則會跳出警告。
* Allocator.Persistent:最慢的配置類型,可以永久存在。
實際建立 NativeArray 的範例如下:
//數值 1 表示 NativeArray 的長度
NativeArray<float> result = new NativeArray<float>(1, Allocator.TempJob);
...
result.Dispose();
因為 NativeContainer 沒有實作 ref return,所以不能直接改變 NativeContainer 的內容。
NativeArray[i]++;
無效,因為其等同於var temp = nativeArray[i]; temp++;
,並沒有真正對 nativeArray[i] 賦予新的數值。
有效的寫法需要像下方範例,將資料複製出來進行操作,再將結果存回去:
MyStruct temp = myNativeArray[i];
temp.memberVariable = 0;
myNativeArray[i] = temp;
我們能夠依照需求實作自己的 NativeContainer,使用 [NativeContainer]
標註我們自訂的結構,並且在結構內實現兩個主要的元素:
Usage tracking
Leak tracking