Unity C# Job System介绍(四) 并行化Job和故障排除(完结)
并行化job
ParallelFor jobs?docs.unity3d.com
當調度Jobs時,只能有一個job來進行一項任務。在游戲中,非常常見的情況是在一個龐大數量的對象上執行一個相同的操作。這里有一個獨立的job類型叫做IJobParallelFor來處理此類問題。ParallelFor jobs當調度Jobs時,只能有一個job來進行一項任務。在游戲中,非常常見的情況是在一個龐大數量的對象上執行一個相同的操作。這里有一個獨立的job類型叫做IJobParallelFor來處理此類問題。
注意:“并行化”job是Unity中所有實現了IJobParallelFor接口的結構的總稱。
一個并行化job使用一個NativeArray存放數據來作為它的數據源。并行化job橫跨多個核心執行。每個核心上有一個job,每個job處理一部分工作量。IJobParallelFor的行為很類似于IJob,但是不同于只執行一個Execute方法,它會在數據源的每一項上執行Execute方法。Execute方法中有一個整數型的參數。這個索引是為了在job的具體操作實現中訪問和操作數據源上的單個元素。
一個定義并行化Job的例子:
struct IncrementByDeltaTimeJob: IJobParallelFor {public NativeArray<float> values;public float deltaTime;public void Execute (int index){float temp = values[index];temp += deltaTime;values[index] = temp;} }調度并行化job
當調度并行化job時,你必須指定你分割NativeArray數據源的長度。在結構中同時存在多個NativeArrayUnity時,C# Job System不知道你要使用哪一個NativeArray作為數據源。這個長度同時會告知C# Job System有多少個Execute方法會被執行。
在這個場景中,并行化job的調度會更復雜。當調度并行化任務時,C# Job System會將工作分成多個批次,分發給不同的核心來處理。每一個批次都包含一部分的Execute方法。隨后C# Job System會在每個CPU核心的Unity原生Job System上調度最多一個job,并傳遞給這個job一些批次的工作來完成。
一個并行化job劃分批次到多個CPU核心
當一個原生job提前完成了分配給它的工作批次后,它會從其他原生job那里獲取其剩余的工作批次。它每次只獲取那個原生job剩余批次的一半,為了確保緩存局部性(cache locality)。
為了優化這個過程,你需要指定一個每批次數量(batch count)。這個每批次數量控制了你會生成多少job和線程中進行任務分發的粒度。使用一個較低的每批次數量,比如1,會使你在線程之間的工作分配更平均。它會帶來一些額外的開銷,所以有時增加每批次數量會是更好的選擇。從每批次數量為1開始,然后慢慢增加這個數量直到性能不再提升是一個合理的策略。
調度并行化job的例子:
job代碼
// Job adding two floating point values together public struct MyParallelJob : IJobParallelFor {[ReadOnly]public NativeArray<float> a;[ReadOnly]public NativeArray<float> b;public NativeArray<float> result;public void Execute(int i){result[i] = a[i] + b[i];} }主線程代碼:
NativeArray<float> a = new NativeArray<float>(2, Allocator.TempJob);NativeArray<float> b = new NativeArray<float>(2, Allocator.TempJob);NativeArray<float> result = new NativeArray<float>(2, Allocator.TempJob);a[0] = 1.1; b[0] = 2.2; a[1] = 3.3; b[1] = 4.4;MyParallelJob jobData = new MyParallelJob(); jobData.a = a; jobData.b = b; jobData.result = result;// Schedule the job with one Execute per index in the results array and only 1 item per processing batch JobHandle handle = jobData.Schedule(result.Length, 1);// Wait for the job to complete handle.Complete();// Free the memory allocated by the arrays a.Dispose(); b.Dispose(); result.Dispose();ParallelForTransform jobs
https://docs.unity3d.com/Manual/JobSystemParallelForTransformJobs.html一個ParallelForTransform?job是另一個類型的ParallelFor?job;它是專門為了Transforms上的操作設計的。
注意:ParallelForTransform job是Unity中所有實現了IJobParallelForTransform接口的結構的總稱。
C# Job System建議和故障排除
C# Job System tips and troubleshooting?docs.unity3d.com
當你使用Unity C# Job System時,確保你遵守以下幾點:C# Job System tips and troubleshooting當你使用Unity C# Job System時,確保你遵守以下幾點:
不要從一個job中訪問靜態的數據
在所有的安全性系統中你都應當避免從一個job中訪問靜態數據。如果你訪問了錯誤的數據,你可能會使Unity崩潰,通常是以意想不到的方式。舉例來說,訪問一個MonoBehaviour可以導致域重新加載時崩潰。
注意:因為這個風險,未來版本的Unity會通過靜態分析來阻止全局變量在job中的訪問。如果你確實在job中訪問了靜態數據,你應當預見到你的代碼會在Unity未來的版本中報錯。
刷新已調度的批次
當你希望你的job開始執行時,你可以通過JobHandle.ScheduleBatchedJobs來刷新已調度的批次。注意調用這個接口時會對性能產生負面的影響。不刷新批次將會延遲調度job,直到主線程開始等待job的結果。在任何其他情況中,你應當調用JobHandle.Complete來開始執行過程。
注意:在Entity Component System(ECS)中批次會暗中為你進行刷新,所以調用JobHandle.ScheduleBatchedJobs是不必要的。
不要試圖去更新NativeContainer的內容
由于缺乏引用返回值,不可能去直接修改一個NativeContainer的內容。例如,nativeArray[0]++ ;和 var temp = nativeArray[0]; temp++;一樣,都沒有更新nativeArray中的值。
你必須從一個index將數據拷貝到一個局部臨時副本,修改這個副本,并將它保存回去,像這樣:
MyStruct temp = myNativeArray[i]; temp.memberVariable = 0; myNativeArray[i] = temp;調用JobHandle.Complete來重新獲得歸屬權
在主線程重新使用數據前,追蹤數據的所有權需要依賴項都完成。只檢查JobHandle.IsCompleted是不夠的。你必須調用JobHandle.Complete來在主線程中重新獲取NaitveContainer類型的所有權。調用Complete同時會清理安全性系統中的狀態。不這樣做的話會造成內存泄漏。這個過程也在你每一幀都調度依賴于上一幀job的新job時被采用。
在主線程中調用Schedule和Complete
你只能在主線程中調用Schedule和Complete方法。如果一個job需要依賴于另一個,使用JobHandle來處理依賴關系而不是嘗試在job中調度新的job。
在正確的時間調用Schedule和Complete
一旦你擁有了一個job所需的數據,盡可能快地在job上調用Schedule,在你需要它的執行結果之前不要調用Complete。一個良好的實踐是調度一個你不需要等待的job,同時它不會與當前正在運行的其他job產生競爭。舉例來說,如果你在一幀結束和下一幀開始之前擁有一段沒有其他job在運行的時間,并且可以接受一幀的延遲,你可以在一幀結束的時候調度一個job,在下一幀中使用它的結果。或者,如果這個轉換時間已經被其他job占滿了,但是在一幀中有一大段未充分利用的時段,在這里調度你的job會更有效率。
將NativeContainer標記為只讀的
記住job在默認情況下擁有NativeContainer的讀寫權限。在合適的NativeContainer上使用[ReadOnly]屬性可以提升性能。
檢查數據的依賴
在Unity的Profiler窗口中,主線程中的"WaitForJobGroup"標簽表明了Unity在等待一個工人線程上的job結束。這個標簽可能意味著你以某種方式引入了一個資源依賴,你需要去解決它。查找JobHandle.Complete來追蹤你在什么地方有資源依賴,導致主線程必須等待。
調試job
job擁有一個Run方法,你可以用它來替代Schedule從而讓主線程立刻執行這個job。你可以使用它來達到調試目的。
不要在job中開辟托管內存
在job中開辟托管內存會難以置信得慢,并且這個job不能利用Unity的Burst編譯器來提升性能。Burst是一個新的基于LLVM的后端編譯器技術,它會使事情對于你更加簡單。它獲取C# job并利用你平臺的特定功能產生高度優化的機器碼。
更多信息
- 觀看Unity GDC 2018: C# Job System的片段列表
- 獲取C# Job Syetem與ECS交互的更進一步信息,查看ECS package documentation on GitHub
總結
以上是生活随笔為你收集整理的Unity C# Job System介绍(四) 并行化Job和故障排除(完结)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 双十一交通银行优惠福利来袭!消费享立减、
- 下一篇: 光大携程菁英白金信用卡好申请吗?想说爱你