Burst用戶指南
原文:https://docs.unity3d.com/Packages/com.unity.burst@0.2/manual/index.html#memory-aliasing-and-noalias
轉自 : https://blog.csdn.net/alph258/article/details/83997917
Unity Burst User Guide
翻譯不易,轉載請注明原譯者。
概觀
Burst是一個編譯器,它使用LLVM將IL / .NET字節(jié)碼轉換為高度優(yōu)化的本機代碼。它作為Unity包發(fā)布,并使用Unity Package Manager集成到Unity中。
快速開始
使用burst編譯器編譯Job
Burst主要用于與Job系統(tǒng)高效協(xié)作。
您可以通過使用屬性[BurstCompile]裝飾Job結構,從而在代碼中簡單地使用burst編譯器 。
using Unity.Burst;using Unity.Collections;using Unity.Jobs;using UnityEngine;public class MyBurst2Behavior : MonoBehaviour
{
void Start()
{
var input = new NativeArray<float>(10, Allocator.Persistent);
var output = new NativeArray<float>(1, Allocator.Persistent);
for (int i = 0; i < input.Length; i++)
input[i] = 1.0f * i;
var job = new MyJob
{
Input = input,
Output = output
};
job.Schedule().Complete();
Debug.Log("The result of the sum is: " + output[0]);
input.Dispose();
output.Dispose();
}
// Using BurstCompile to compile a Job with burst
// Set CompileSynchronously to true to make sure that the method will not be compiled asynchronously
// but on the first schedule
[BurstCompile(CompileSynchronously = true)]
private struct MyJob : IJob
{
[ReadOnly]
public NativeArray<float> Input;
[WriteOnly]
public NativeArray<float> Output;
public void Execute()
{
float result = 0.0f;
for (int i = 0; i < Input.Length; i++)
{
result += Input[i];
}
Output[0] = result;
}
}
}
默認情況下,在編輯器中,Burst JIT是通過異步來編譯job,但在上面的示例中,我們使用該選項CompileSynchronously = true確保在第一個Schedule中編譯該方法。通常,您應該使用異步編譯。見[BurstCompile]選項
Jobs/Burst Menu菜單
Burst在Jobs菜單中添加了一些菜單項:
- 使用Burst Jobs:選中此項后,具有該屬性[BurstCompile]的Jobs將被Burst編譯。默認是勾選的。
- Burst Inspector:打開Burst Inspector窗口
- 啟用Burst 安全檢查:選中此選項后,將使用收集容器(例如NativeArray)的代碼將檢查安全用法,尤其是Jobs數(shù)據(jù)依賴性檢查系統(tǒng)和容器索引超出界限。請注意,此選項默認情況下禁用noaliasing性能優(yōu)化。默認是勾選的。
- 啟用Burst 編譯:選中此選項后,Burst 將編譯Jobs和使用該屬性[BurstCompile]標記的自定義委托。默認是勾選的。
-
顯示Burst 耗時:當選中此項時,每次Burst
都必須在編輯器中JIT編譯一個Jobs,編譯此方法所需的時間將顯示在日志中。默認為未勾選的。
Burst 屬性面板
從“Jobs”菜單中,您可以打開Burst 屬性面板。屬性面板允許您查看可以編譯的所有作業(yè),然后您還可以檢查生成的本機代碼。
在左側窗格中,我們有Compile Targets,它提供了一個可以編譯的Jobs列表。以白色突出顯示的作業(yè)可以通過Burst 編譯,而禁用的作業(yè)則不具有該[BurstCompile]屬性。
1.從左窗格中選擇一個活動的編譯目標。
2.在右窗格中,按“ 刷新反匯編 ”按鈕
3.在不同選項卡之間切換以顯示詳細信息:
- 選項卡程序集(Assembly )提供了由burst生成的最終優(yōu)化本機代碼
- 選項卡.NET IL提供了從Job方法中提取的原始.NET IL的視圖
- 選項卡LLVM(未優(yōu)化)在優(yōu)化之前提供內(nèi)部LLVM IR的視圖。
- 選項卡LLVM(優(yōu)化)在優(yōu)化后提供內(nèi)部LLVM IR的視圖。
- 選項卡LLVM IR Optimization Diagnostics提供優(yōu)化的詳細LLVM診斷(即,如果它們成功或失敗)。
4.您還可以切換不同的選項:
- 如果啟用“Safety Checks”將生成包括容器訪問安全檢查(如檢查是否有作業(yè)寫入本地容器是只讀)的代碼
- 如果啟用“Optimizations ”此選項將允許編譯器優(yōu)化代碼。
- 如果啟用了“ Fast Math”選項,則編譯器可以折疊數(shù)學運算以提高效率,但代價是不考慮精確的數(shù)學正確性(請參閱編譯器放寬選項)
C#/ .NET語言支持
Burst正在研究.NET的一個子集,它不允許在代碼中使用任何托管對象/引用類型(C#中的類)。
以下部分提供了更多有關burst實際支持的構造類型詳細信息。
支持的.NET類型
原始類型
Burst支持以下原始類型:
- bool
- char
- sbyte/byte
- short/ushort
- int/uint
- long/ulong
- float
- double
Burst不支持以下類型: - string 因為這是一種托管類型
- decimal
矢量類型
Burst能夠將矢量類型從Unity.Mathematics原生SIMD矢量類型轉換為優(yōu)化的第一類支持:
- bool2/bool3/bool4
- uint2/uint3/uint4
- int2/int3/int4
- float2/float3/float4
請注意,出于性能原因,應首選4種wide 類型(float4,int4…)
枚舉類型
Burst支持所有枚舉,包括具有特定存儲類型的枚舉(例如public enum MyEnum : short)
Burst目前不支持Enum方法(例如Enum.HasFlag)
結構類型
Burst支持具有支持類型的任何字段的常規(guī)結構。
Burst支持固定數(shù)組字段。
關于布局,LayoutKind.Sequential和LayoutKind.Explicit都受到支持,該StructLayout.Pack包裝尺寸不支持
本機支持System.IntPtr和UIntPtr作為直接表示指針的內(nèi)部結構。
指針類型
Burst支持任何Burst支持類型的指針類型
通用類型
Burst支持結構使用的泛型類型。具體來說,它支持對具有接口約束的泛型類型的泛型調用的完全實例化(例如,當具有通用參數(shù)的結構需要實現(xiàn)接口時)
數(shù)組類型
Burst不支持托管陣列。例如,您應該使用本機容器NativeArray。
語言支持
Burst支持以下代碼流和語法:
- 常規(guī)C??刂屏鞒蹋篿f/else/switch/case/for/while/break/continue
- 擴展方法
- 不安全的代碼,指針操作…等等。
- 結構的實例方法
- 通過ref / out參數(shù)
- 調用icall / internal函數(shù)
- throw假設簡單的拋出模式(例如throw new ArgumentException(“Invalid
argument”)),對表達式的支持有限。在這種情況下,我們將嘗試提取靜態(tài)字符串異常消息以將其包含在生成的代碼中。 - 一些特殊的操作碼IL像cpblk,initblk,sizeof
- 從靜態(tài)只讀字段加載
Burst不支持: - DllImport或calli(這應該在將來的版本中支持)
- catch
- try/ finally(將來某個時候)
- foreach因為它需要try/ finally(這應該在未來的版本中支持)
- 從非只讀靜態(tài)字段加載或存儲到靜態(tài)字段
- 任何與托管對象相關的方法(例如數(shù)組訪問等)
內(nèi)部函數(shù)
System.Math
Burst為聲明的所有方法提供內(nèi)在函數(shù),System.Math但不支持以下方法:
- double IEEERemainder(double x, double y)
- Round(double value, int digits)
System.IntPtr
Burst支持System.IntPtr/的所有方法System.UIntPtr,包括靜態(tài)字段IntPtr.Zero和IntPtr.Size
System.Threading.Interlocked
Burst支持由System.Threading.Interlocked(例如Interlocked.Increment…等)提供的所有方法的原子內(nèi)存內(nèi)在函數(shù)
NativeArray
Burst 僅支持以下NativeArray方法的有noalias的內(nèi)在函數(shù):
- int Length { get; }
- T this[int index] { get; set; }
任何其他成員的使用都將noalias自動禁用優(yōu)化。
優(yōu)化指南
內(nèi)存別名(Memory Aliasing)和 noalias
Memory aliasing是一個重要的概念,可以為編譯器提供重要的優(yōu)化,使編譯器知道代碼如何使用數(shù)據(jù)。
問題
讓我們舉一個簡單的例子,將數(shù)據(jù)從輸入數(shù)組復制到輸出數(shù)組:
[BurstCompile]private struct CopyJob : IJob
{
[ReadOnly]
public NativeArray<float> Input;
[WriteOnly]
public NativeArray<float> Output;
public void Execute()
{
for (int i = 0; i < Input.Length; i++)
{
Output[i] = Input[i];
}
}
}
翻譯不易,轉載請注明原譯者alph258。
沒有內(nèi)存別名:
如果兩個數(shù)組Input并Output沒有輕微重疊,這意味著它們各自的內(nèi)存位置沒有別名,我們將在示例輸入/輸出上運行此作業(yè)后得到以下結果:

自動矢量化器沒有內(nèi)存別名:
現(xiàn)在,如果編譯器是noalias喚醒,它將能夠通過所謂的向量化來優(yōu)化先前的標量循環(huán)(在標量級別工作):編譯器將代表您重寫循環(huán)以按小批量處理元素(工作在矢量級別,例如4乘4個元素)像這樣:

內(nèi)存別名:
接下來,如果由于某些原因(今天很困難的將JobSystem引入),輸出數(shù)組實際上是將一個元素與輸入數(shù)組重疊(例如Output[0]實際指向的點Input[1])意味著內(nèi)存是別名,我們將得到以下內(nèi)容運行時的結果CopyJob(假設自動矢量化器沒有運行):

使用無效矢量化代碼的內(nèi)存別名:
更糟糕的是,如果編譯器不知道這種內(nèi)存別名,它仍然會嘗試自動向量化循環(huán),我們會得到以下結果,這與以前的標量版本不同:

此代碼的結果將無效,如果編譯器未識別它們,則可能導致非常嚴重的錯誤。
解決方案burst和JobSystem
為了確保Job可以安全地進行矢量化(當有循環(huán)時),burst依賴于:
- JobSystem的安全性的假設您可以在作業(yè)中指定輸入/輸出中的數(shù)據(jù):這意味著默認情況下,通過作業(yè)安全訪問的所有數(shù)據(jù)都不是別名
- 進一步分析代碼的burst,以確保代碼也是安全的 burst中的別名分析目前依賴于您的代碼需要遵循的一些約束,以便讓自動矢量化器正常工作:
- 只有NativeArray被使用,且只有屬性Length或索引this[index]被使用
- 不應將本機容器(例如NativeArray)或間接包含容器的結構復制到局部變量
- 本地容器可以通過值傳遞給方法參數(shù),在所有參數(shù)都來自標識源的條件下,這些參數(shù)來自靜態(tài)方法的字段或其他參數(shù),但不是兩者都是,并且方法是靜態(tài)的
- Native Containers或間接包含容器的struct不會存儲到struct的字段中
- 假設在“ Jobs”菜單中取消選中“ Enable Burst Safety Checks” 選項
我們期望通過更細粒度的模型來改進別名分析,這將允許放松一些這些約束。
使用noalias分析生成代碼的示例
讓我們以CopyJob編譯到本機代碼并禁用noalias分析為例。
以下循環(huán)是x64使用啟用AVX2的noalias analysis enabled的指令進行編譯的結果:(注意我們只復制核心循環(huán),而不是整個方法的完整序言和結尾)
該指令vmovups在這里移動了8個浮點數(shù),因此單個自動向量化循環(huán)現(xiàn)在移動4 x 8 = 每個循環(huán)迭代復制32個浮點數(shù)而不是一個?。ㄒ虼?,相對于與原始循環(huán),將有/ 32次循環(huán)步驟迭代)
.LBB0_4:
vmovups ymm0, ymmword ptr [rcx - 96]
vmovups ymm1, ymmword ptr [rcx - 64]
vmovups ymm2, ymmword ptr [rcx - 32]
vmovups ymm3, ymmword ptr [rcx]
vmovups ymmword ptr [rdx - 96], ymm0
vmovups ymmword ptr [rdx - 64], ymm1
vmovups ymmword ptr [rdx - 32], ymm2
vmovups ymmword ptr [rdx], ymm3
sub rdx, -128
sub rcx, -128
add rsi, -32
jne .LBB0_4
test r10d, r10d
je .LBB0_8
禁用noalias分析的相同循環(huán)將每次循環(huán)迭代僅復制一個浮點數(shù):
.LBB0_2:
mov r8, qword ptr [rcx]
mov rdx, qword ptr [rcx + 16]
cdqe
mov edx, dword ptr [rdx + 4*rax]
mov dword ptr [r8 + 4*rax], edx
inc eax
cmp eax, dword ptr [rcx + 8]
jl .LBB0_2
我們可以看到,這里的性能差異很大。這就是為什么noalias 意識到本機代碼生成是基礎,而這正是burst 試圖解決的問題。
編譯器選項
編譯作業(yè)時,可以更改編譯器的行為:
- 對數(shù)學函數(shù)使用不同的精度(sin,cos …)
- 允許編譯器通過放寬數(shù)學計算的順序來重新排列浮點計算。
- 強制同步編譯作業(yè)(僅適用于編輯器/ JIT案例)
- 使用內(nèi)部編譯器選項(尚未詳細)
這些標簽可以通過使用 [BurstCompile] 屬性來設置,比如 [BurstCompile(Accuracy.Med, Support.Relaxed)]
準確性
準確性由以下枚舉定義:
public enum Accuracy
{
Std,
Low,
Med,
High,
}
目前,實施僅提供以下準確性:
- Std提供1 ULP的準確度。這是默認值。
- High,Med,Low正在提供3.5 ULP的精度
High對于大多數(shù)游戲來說,使用準確度應該足夠了。
ULP(最后位置的單位或最小精度的單位)是浮點數(shù)之間的間隔,即,一個值的最小精度數(shù)字決定了它是否為1.
我們希望支持更多的ULP準確性Med和Low突發(fā)的未來版本
編譯器放松
編譯器松弛由以下枚舉定義:
public enum Support
{
Strict,
Relaxed
}
- 嚴格:編譯器沒有執(zhí)行任何計算的重新排列,并且編譯器在關注特殊浮點值(非正常,NaN …)時要小心。這是默認值
- 放松:編譯器可以執(zhí)行指令重新排列 和/或 使用 專用或者不太精確 的硬件SIMD指令。
通常,某些硬件可以支持乘法和加法(例如mad a * b + c)到單個指令中。使用寬松計算可以允許這些優(yōu)化。重新排序這些指令會導致精度降低。
使用Relaxed編譯器松弛可以用于許多場景,其中嚴格要求計算的確切順序和NaN值的統(tǒng)一處理。
同步編譯
默認情況下,編輯器中的burst 編譯器是采用異步進行編譯作業(yè)的。
您可以通過設置CompileSynchronously = true的[BurstCompile]屬性改變這種行為:
[BurstCompile(CompileSynchronously = true)]public struct MyJob : IJob
{
// ...
}
Unity.Mathematics
所述Unity.Mathematics提供矢量類型(float4,float3被直接映射到硬件寄存器SIMD …)。
此外,來自math類型的許多功能也直接映射到硬件SIMD指令。
請注意,目前,該庫的最佳使用,建議使用SIMD 4位寬類型(float4,int4,bool4…)
獨立播放器支持
burst 編譯器支持獨立播放器。
用法
在構建播放器時,burst將為游戲中的所有突發(fā)作業(yè)編譯單個動態(tài)庫。根據(jù)平臺的不同,動態(tài)庫將輸出到不同的文件夾(在Windows上,它位于路徑中Data/Plugins/lib_burst_generated.dll)
jobs系統(tǒng)運行時將在由burst編譯的第一個作業(yè)上加載此庫。
啟用編譯的設置由Jobs/burst 菜單控制,與編輯器相同。
在將來的迭代中,這些設置將被移動到每個平臺/播放器的適當設置。
支持的平臺
Burst支持以下獨立播放器平臺:
- 視窗
- MacOS的
- Linux的
- Xbox One
- PS4
- Android(ARM v7和v8 +)
- iOS(ARM v7和v8 +)
已知的問題
- 目前不支持精度/精度
- 目標CPU當前是每個平臺的硬編碼(例如,Windows 64位的SSE4)
這些已知問題將在未來的burst版本中得到解決。