Unity Burst 用户指南

Burst用户指南

原文:https://docs.unity3d.com/Packages/com.unity.burst@0.2/manual/index.html#memory-aliasing-and-noalias
Unity Burst User Guide
翻译不易,转载请注明原译者。

概观

Burst是一个编译器,它使用LLVM将IL / .NET字节码转换为高度优化的本机代码。它作为Unity包发布,并使用Unity Package Manager集成到Unity中。

快速开始

使用burst编译器编译Job

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<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数据依赖性检查系统和容器索引超出界限。请注意,此选项默认情况下禁用noaliasing性能优化。默认是勾选的。
  • 启用Burst 编译:选中此选项后,Burst 将编译Jobs和使用该属性[BurstCompile]标记的自定义委托。默认是勾选的。
  • 显示Burst 耗时:当选中此项时,每次Burst
    都必须在编辑器中JIT编译一个Jobs,编译此方法所需的时间将显示在日志中。默认为未勾选的。

Burst 属性面板

从“Jobs”菜单中,您可以打开Burst 属性面板。属性面板允许您查看可以编译的所有作业,然后您还可以检查生成的本机代码。
在这里插入图片描述
在左侧窗格中,我们有Compile Targets,它提供了一个可以编译的Jobs列表。以白色突出显示的作业可以通过Burst 编译,而禁用的作业则不具有该[BurstCompile]属性。
1.从左窗格中选择一个活动的编译目标。
2.在右窗格中,按“ 刷新反汇编 ”按钮
3.在不同选项卡之间切换以显示详细信息:

  • 选项卡程序集(Assembly )提供了由burst生成的最终优化本机代码
  • 选项卡**.NET IL**提供了从Job方法中提取的原始.NET IL的视图
  • 选项卡LLVM(未优化)在优化之前提供内部LLVM IR的视图。
  • 选项卡**LLVM(优化)**在优化后提供内部LLVM IR的视图。
  • 选项卡LLVM IR Optimization Diagnostics提供优化的详细LLVM诊断(即,如果它们成功或失败)。

4.您还可以切换不同的选项:

  • 如果启用“Safety Checks”将生成包括容器访问安全检查(如检查是否有作业写入本地容器是只读)的代码
  • 如果启用“Optimizations ”此选项将允许编译器优化代码。
  • 如果启用了“ Fast Math”选项,则编译器可以折叠数学运算以提高效率,但代价是不考虑精确的数学正确性(请参阅编译器放宽选项)

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矢量类型转换为优化的第一类支持:

  • 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支持具有支持类型的任何字段的常规结构。
Burst支持固定数组字段。
关于布局,LayoutKind.Sequential和LayoutKind.Explicit都受到支持,该StructLayout.Pack包装尺寸不支持
本机支持System.IntPtr和UIntPtr作为直接表示指针的内部结构。

指针类型

Burst支持任何Burst支持类型的指针类型

通用类型

Burst支持结构使用的泛型类型。具体来说,它支持对具有接口约束的泛型类型的泛型调用的完全实例化(例如,当具有通用参数的结构需要实现接口时)

数组类型

Burst不支持托管阵列。例如,您应该使用本机容器NativeArray。

语言支持

Burst支持以下代码流和语法:

  • 常规C#控制流程:if/else/switch/case/for/while/break/continue
  • 扩展方法
  • 不安全的代码,指针操作…等等。
  • 结构的实例方法
  • 通过ref / out参数
  • 调用icall / internal函数
  • throw假设简单的抛出模式(例如throw new ArgumentException(“Invalid
    argument”)),对表达式的支持有限。在这种情况下,我们将尝试提取静态字符串异常消息以将其包含在生成的代码中。
  • 一些特殊的操作码IL像cpblk,initblk,sizeof
  • 从静态只读字段加载
    Burst不支持:
  • DllImport或calli(这应该在将来的版本中支持)
  • catch
  • try/ finally(将来某个时候)
  • foreach因为它需要try/ finally(这应该在未来的版本中支持)
  • 从非只读静态字段加载或存储到静态字段
  • 任何与托管对象相关的方法(例如数组访问等)

内部函数

System.Math

Burst为声明的所有方法提供内在函数,System.Math但不支持以下方法:

  • double IEEERemainder(double x, double y)
  • Round(double value, int digits)

System.IntPtr

Burst支持System.IntPtr/的所有方法System.UIntPtr,包括静态字段IntPtr.Zero和IntPtr.Size

System.Threading.Interlocked

Burst支持由System.Threading.Interlocked(例如Interlocked.Increment…等)提供的所有方法的原子内存内在函数
NativeArray
Burst 仅支持以下NativeArray方法的有noalias的内在函数:

  • int Length { get; }
  • T this[int index] { get; set; }
    任何其他成员的使用都将noalias自动禁用优化。

优化指南

内存别名(Memory Aliasing)和 noalias

Memory aliasing是一个重要的概念,可以为编译器提供重要的优化,使编译器知道代码如何使用数据。

问题

让我们举一个简单的例子,将数据从输入数组复制到输出数组:

[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。

没有内存别名:

如果两个数组Input并Output没有轻微重叠,这意味着它们各自的内存位置没有别名,我们将在示例输入/输出上运行此作业后得到以下结果:
在这里插入图片描述

自动矢量化器没有内存别名:

现在,如果编译器是noalias唤醒,它将能够通过所谓的向量化来优化先前的标量循环(在标量级别工作):编译器将代表您重写循环以按小批量处理元素(工作在矢量级别,例如4乘4个元素)像这样:
在这里插入图片描述

内存别名:

接下来,如果由于某些原因(今天很困难的将JobSystem引入),输出数组实际上是将一个元素与输入数组重叠(例如Output[0]实际指向的点Input[1])意味着内存是别名,我们将得到以下内容运行时的结果CopyJob(假设自动矢量化器没有运行):
在这里插入图片描述

使用无效矢量化代码的内存别名:

更糟糕的是,如果编译器不知道这种内存别名,它仍然会尝试自动向量化循环,我们会得到以下结果,这与以前的标量版本不同:
在这里插入图片描述
此代码的结果将无效,如果编译器未识别它们,则可能导致非常严重的错误。

解决方案burst和JobSystem

为了确保Job可以安全地进行矢量化(当有循环时),burst依赖于:

  • JobSystem的安全性的假设您可以在作业中指定输入/输出中的数据:这意味着默认情况下,通过作业安全访问的所有数据都不是别名
  • 进一步分析代码的burst,以确保代码也是安全的 burst中的别名分析目前依赖于您的代码需要遵循的一些约束,以便让自动矢量化器正常工作:
  • 只有NativeArray被使用,且只有属性Length或索引this[index]被使用
  • 不应将本机容器(例如NativeArray)或间接包含容器的结构复制到局部变量
  • 本地容器可以通过值传递给方法参数,在所有参数都来自标识源的条件下,这些参数来自静态方法的字段或其他参数,但不是两者都是,并且方法是静态的
  • Native Containers或间接包含容器的struct不会存储到struct的字段中
  • 假设在“ Jobs”菜单中取消选中“ Enable Burst Safety Checks” 选项
    我们期望通过更细粒度的模型来改进别名分析,这将允许放松一些这些约束。

使用noalias分析生成代码的示例

让我们以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 试图解决的问题。

编译器选项

编译作业时,可以更改编译器的行为:

  • 对数学函数使用不同的精度(sin,cos …)
  • 允许编译器通过放宽数学计算的顺序来重新排列浮点计算。
  • 强制同步编译作业(仅适用于编辑器/ JIT案例)
  • 使用内部编译器选项(尚未详细)
    这些标签可以通过使用 [BurstCompile] 属性来设置,比如 [BurstCompile(Accuracy.Med, Support.Relaxed)]

准确性

准确性由以下枚举定义:

public enum Accuracy
{
    Std,
    Low,
    Med,
    High,
}

目前,实施仅提供以下准确性:

  • Std提供1 ULP的准确度。这是默认值
  • High,Med,Low正在提供3.5 ULP的精度

High对于大多数游戏来说,使用准确度应该足够了。
ULP(最后位置的单位或最小精度的单位)是浮点数之间的间隔,即,一个值的最小精度数字决定了它是否为1.
我们希望支持更多的ULP准确性Med和Low突发的未来版本

编译器放松

编译器松弛由以下枚举定义:

public enum Support
{
    Strict,
    Relaxed
}
  • 严格:编译器没有执行任何计算的重新排列,并且编译器在关注特殊浮点值(非正常,NaN …)时要小心。这是默认值
  • 放松:编译器可以执行指令重新排列 和/或 使用 专用或者不太精确 的硬件SIMD指令。

通常,某些硬件可以支持乘法和加法(例如mad a * b + c)到单个指令中。使用宽松计算可以允许这些优化。重新排序这些指令会导致精度降低。
使用Relaxed编译器松弛可以用于许多场景,其中严格要求计算的确切顺序和NaN值的统一处理。

同步编译

默认情况下,编辑器中的burst 编译器是采用异步进行编译作业的。
您可以通过设置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将为游戏中的所有突发作业编译单个动态库。根据平台的不同,动态库将输出到不同的文件夹(在Windows上,它位于路径中Data/Plugins/lib_burst_generated.dll)
jobs系统运行时将在由burst编译的第一个作业上加载此库。
启用编译的设置由Jobs/burst 菜单控制,与编辑器相同。
在将来的迭代中,这些设置将被移动到每个平台/播放器的适当设置。

支持的平台

Burst支持以下独立播放器平台:

  • 视窗
  • MacOS的
  • Linux的
  • Xbox One
  • PS4
  • Android(ARM v7和v8 +)
  • iOS(ARM v7和v8 +)

已知的问题

  • 目前不支持精度/精度
  • 目标CPU当前是每个平台的硬编码(例如,Windows 64位的SSE4)
    这些已知问题将在未来的burst版本中得到解决。
  • 3
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值