原文:https://docs.unity3d.com/Packages/[email protected]/manual/index.html#memory-aliasing-and-noalias
Unity Burst User Guide
翻译不易,转载请注明原译者。
Burst是一个编译器,它使用LLVM将IL / .NET字节码转换为高度优化的本机代码。它作为Unity包发布,并使用Unity Package Manager集成到Unity中。
Burst主要用于与Job系统高效协作。
您可以通过使用属性[BurstCompile]装饰Job结构,从而在代码中简单地使用burst编译器 。
using Unity.Burst;using Unity.Collections;using Unity.Jobs;using UnityEngine;public class MyBurst2Behavior : MonoBehaviour
{
void Start()
{
var input = new NativeArray(10, Allocator.Persistent);
var output = new NativeArray(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 Input;
[WriteOnly]
public NativeArray 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]选项
Burst在Jobs菜单中添加了一些菜单项:
从“Jobs”菜单中,您可以打开Burst 属性面板。属性面板允许您查看可以编译的所有作业,然后您还可以检查生成的本机代码。
在左侧窗格中,我们有Compile Targets,它提供了一个可以编译的Jobs列表。以白色突出显示的作业可以通过Burst 编译,而禁用的作业则不具有该[BurstCompile]属性。
1.从左窗格中选择一个活动的编译目标。
2.在右窗格中,按“ 刷新反汇编 ”按钮
3.在不同选项卡之间切换以显示详细信息:
4.您还可以切换不同的选项:
Burst正在研究.NET的一个子集,它不允许在代码中使用任何托管对象/引用类型(C#中的类)。
以下部分提供了更多有关burst实际支持的构造类型详细信息。
Burst支持以下原始类型:
Burst能够将矢量类型从Unity.Mathematics原生SIMD矢量类型转换为优化的第一类支持:
Burst支持所有枚举,包括具有特定存储类型的枚举(例如public enum MyEnum : short)
Burst目前不支持Enum方法(例如Enum.HasFlag)
Burst支持具有支持类型的任何字段的常规结构。
Burst支持固定数组字段。
关于布局,LayoutKind.Sequential和LayoutKind.Explicit都受到支持,该StructLayout.Pack包装尺寸不支持
本机支持System.IntPtr和UIntPtr作为直接表示指针的内部结构。
Burst支持任何Burst支持类型的指针类型
Burst支持结构使用的泛型类型。具体来说,它支持对具有接口约束的泛型类型的泛型调用的完全实例化(例如,当具有通用参数的结构需要实现接口时)
Burst不支持托管阵列。例如,您应该使用本机容器NativeArray。
Burst支持以下代码流和语法:
Burst为声明的所有方法提供内在函数,System.Math但不支持以下方法:
Burst支持System.IntPtr/的所有方法System.UIntPtr,包括静态字段IntPtr.Zero和IntPtr.Size
Burst支持由System.Threading.Interlocked(例如Interlocked.Increment…等)提供的所有方法的原子内存内在函数
NativeArray
Burst 仅支持以下NativeArray方法的有noalias的内在函数:
Memory aliasing是一个重要的概念,可以为编译器提供重要的优化,使编译器知道代码如何使用数据。
让我们举一个简单的例子,将数据从输入数组复制到输出数组:
[BurstCompile]private struct CopyJob : IJob
{
[ReadOnly]
public NativeArray Input;
[WriteOnly]
public NativeArray Output;
public void Execute()
{
for (int i = 0; i < Input.Length; i++)
{
Output[i] = Input[i];
}
}
}
翻译不易,转载请注明原译者alph258。
如果两个数组Input并Output没有轻微重叠,这意味着它们各自的内存位置没有别名,我们将在示例输入/输出上运行此作业后得到以下结果:
现在,如果编译器是noalias唤醒,它将能够通过所谓的向量化来优化先前的标量循环(在标量级别工作):编译器将代表您重写循环以按小批量处理元素(工作在矢量级别,例如4乘4个元素)像这样:
接下来,如果由于某些原因(今天很困难的将JobSystem引入),输出数组实际上是将一个元素与输入数组重叠(例如Output[0]实际指向的点Input[1])意味着内存是别名,我们将得到以下内容运行时的结果CopyJob(假设自动矢量化器没有运行):
更糟糕的是,如果编译器不知道这种内存别名,它仍然会尝试自动向量化循环,我们会得到以下结果,这与以前的标量版本不同:
此代码的结果将无效,如果编译器未识别它们,则可能导致非常严重的错误。
为了确保Job可以安全地进行矢量化(当有循环时),burst依赖于:
让我们以CopyJob编译到本机代码并禁用noalias分析为例。
以下循环是x64使用启用AVX2的noalias analysis enabled的指令进行编译的结果:(注意我们只复制核心循环,而不是整个方法的完整序言和结尾)
该指令vmovups在这里移动了8个浮点数,因此单个自动向量化循环现在移动4 x 8 = 每个循环迭代复制32个浮点数而不是一个!(因此,相对于与原始循环,将有/ 32次循环步骤迭代)
.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分析的相同循环将每次循环迭代仅复制一个浮点数:
.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 试图解决的问题。
编译作业时,可以更改编译器的行为:
准确性由以下枚举定义:
public enum Accuracy
{
Std,
Low,
Med,
High,
}
目前,实施仅提供以下准确性:
High对于大多数游戏来说,使用准确度应该足够了。
ULP(最后位置的单位或最小精度的单位)是浮点数之间的间隔,即,一个值的最小精度数字决定了它是否为1.
我们希望支持更多的ULP准确性Med和Low突发的未来版本
编译器松弛由以下枚举定义:
public enum Support
{
Strict,
Relaxed
}
通常,某些硬件可以支持乘法和加法(例如mad a * b + c)到单个指令中。使用宽松计算可以允许这些优化。重新排序这些指令会导致精度降低。
使用Relaxed编译器松弛可以用于许多场景,其中严格要求计算的确切顺序和NaN值的统一处理。
默认情况下,编辑器中的burst 编译器是采用异步进行编译作业的。
您可以通过设置CompileSynchronously = true的[BurstCompile]属性改变这种行为:
[BurstCompile(CompileSynchronously = true)]public struct MyJob : IJob
{
// ...
}
所述Unity.Mathematics提供矢量类型(float4,float3被直接映射到硬件寄存器SIMD …)。
此外,来自math类型的许多功能也直接映射到硬件SIMD指令。
请注意,目前,该库的最佳使用,建议使用SIMD 4位宽类型(float4,int4,bool4…)
burst 编译器支持独立播放器。
在构建播放器时,burst将为游戏中的所有突发作业编译单个动态库。根据平台的不同,动态库将输出到不同的文件夹(在Windows上,它位于路径中Data/Plugins/lib_burst_generated.dll)
jobs系统运行时将在由burst编译的第一个作业上加载此库。
启用编译的设置由Jobs/burst 菜单控制,与编辑器相同。
在将来的迭代中,这些设置将被移动到每个平台/播放器的适当设置。
Burst支持以下独立播放器平台: