【測試】使用 Native Arrays 引起cpu處理過大?

前言

創建一個Job,使用NativeArray儲存一個整數,然後呼叫了1億次加法,發現對比於使用int來呼叫1億次,相差了10倍左右,呼叫1億次加法大概花了600毫秒,但是使用NativeArray來算1億次加法會花大概6000毫秒,於是就有個疑問了 – NativeArray呼叫太多次是不是會造成cpu處理過大?

結論

Editor模式中,NativeContainer會有Safety check,但在建置完後的程式是沒有Safety check的,所以!!! 在Editor模式如果呼叫了過多次NativeContainer造成遊戲之中有延遲,很有可能是因為Safety check造成的,實際上性能消耗還是build出來測試會比較準確!

測試幾種可能

1. 在Job中int計算1億次

先創建一個Job,用它來計算一般用int的性能消耗

1
2
3
4
5
6
7
8
9
10
11
public struct OnlyCalculateJob : IJob
{
public int result;
public void Execute()
{
for (int i = 0; i < 100000000; i++)
{
result += 5;
}
}
}

並在Start時呼叫

1
2
3
4
5
6
private void Start()
{
OnlyCalculateJob jobData = new OnlyCalculateJob();
JobHandle handle = jobData.Schedule();
handle.Complete();
}

可以看到大約消耗了600毫秒

2. 在Start中int計算1億次

1
2
3
4
5
6
7
8
private void Start()
{
int Result = 0;
for (int i = 0; i < 100000000; i++)
{
Result += 5;
}
}


也是差不多600毫秒 ((這個測試好像沒什麼意義XD))

3. NativeArray< int> 執行一億次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 private void Start()
{
NativeArray<int> result = new NativeArray<int>(1, Allocator.TempJob);
NativeCalculateJob jobData = new NativeCalculateJob();
jobData.result = result;
JobHandle handle = jobData.Schedule();
handle.Complete();
result.Dispose();
}

public struct NativeCalculateJob : IJob
{
public NativeArray<int> result;
public void Execute()
{
for (int i = 0; i < 100000000; i++)
{
result[0] = result[0] + 5;
}
}
}

這個就有差了,整整會卡住七秒左右
這邊推論應該是NativeArray造成的,測看看如果減少NativeArray次數,試試看會不會減少處理速度!

3.使用另外一個int運算,再將結果丟給NativeArray

1
2
3
4
5
6
7
8
9
10
11
12
13
public struct NativeCalculateJob : IJob
{
public NativeArray<int> result;
private int _result;
public void Execute()
{
for (int i = 0; i < 100000000; i++)
{
_result += 5;
result[0] = _result;
}
}
}

這邊比上面第2點還少呼叫了一次NativeArray,所以如果處理速度增加,那應該很確定是NativeArray造成的了

花了3800毫秒,看來的確是少差不多一半,那現在如果增加呼叫NativeArray的次數,那應該時間也會加倍吧?!

4. 增加NativeArray呼叫次數試試看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public struct NativeCalculateJob : IJob
{
public NativeArray<int> result;
private int _result;
public void Execute()
{
for (int i = 0; i < 100000000; i++)
{
_result += 5;
result[0] = _result;
result[0] = _result;
result[0] = _result;
}
Debug.Log(result[0]);
}
}

超級沒意義的增加XD
而結果果然處理速度也是倍增

花了將近10秒!!!

測試到這邊的時候,我以為結論是NativeArray要小心地增加他的呼叫次數,不能像上面那樣隨意的亂放((一般也不會有人這樣寫)),然後想來想去都覺得不對,就去查詢了一下為什麼NativeArray會消耗這麼多性能,才發現!!!

其實並不是NativeArray會消耗很多性能,而是在編輯器(Editor)模式下,會有Safety checks,一種安全檢查,他會消耗很多效能,但在build出來後會disable掉這個safety checks.

引述在Unity官方討論區看到的Unity工程師的說法

Safety checks in the editor have a significant performance cost.

The safety checks are disabled in the standalone player completely and in IL2CPP there is a fast path making builtin arrays and NativeArrays equally fast.

The real performance gains of NativeArray are leveraged from the burst compiler, when writing primarily jobified code with the [ComputeOptimization] attribute. We expect that for any code that is performance sensitive that developers will write it to run in a job in burst.

In burst the speed gains from using NativeArray are very significant. Usually on the order of 5-15x compared to il2cpp / mono.

因此我就又測試了一下,同樣一段CODE,在編輯器模式跟在Android裝置上測出來的性能

放在編輯器下

放在android裝置下

有興趣的可以看下方連結

How NativeContainer are so fast even it is allocated in every frame?

NativeArray vs DynamicBuffer : Which access data faster?

Why am I not seeing a performance increase with the Job System?

Job System not as fast as mine, why not?

Native Arrays approximately an order of magnitude slower than arrays