美文网首页
Unity Burst 用户指南(转)

Unity Burst 用户指南(转)

作者: HaruHappy | 来源:发表于2018-12-02 18:28 被阅读0次

    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字节码转换为高度优化的本机代码。它作为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版本中得到解决。

    相关文章

      网友评论

          本文标题:Unity Burst 用户指南(转)

          本文链接:https://www.haomeiwen.com/subject/dofscqtx.html