Unity Burst技术(持续更新...)

910 阅读3分钟

Unity Burst

What's Burst

首先需要了解的是,unity burst是一种基于llvm的编译器优化解决方案,在使用了burst技术之后,从.NET的IL到Native代码的流程大致如下:

img_v2_1acb0e9b-cc75-491e-86db-ed14205a8a9g.jpg

简单来说,burst技术就是在由c#代码生成的.NET IL之后,以及在il2cpp执行之前的时间段中,对代码进行优化。

不过为什么不是在il2cpp层面对此进行的优化?对于这个问题,官方也相对给出了解释如下:

img_v2_3fd6565f-52c7-4989-b835-5ac2e1c86bag.jpg

简单来说,就是IL2CPP的设计初衷并不是极致地追求性能,而是最大程度地为了平台地兼容性(IOS)。

How Burst work?

因为Burst代码还是在c#层面,所以需要在c#语言层面对此提供相应的支持,unity官方将这一技术称之为 IL/C# Subset, 这种Subset也被称作HPC#(High Performance C#)。

img_v2_6f7d677f-b628-4e4b-9834-841b056353eg.jpg

之所以HPC#中带有(HP),是因为相较于普通的c#,HPC具有以下几个特点:

  1. No class types
  2. No boxing
  3. No GC allocation
  4. No exceptions for control flow

可以看到,HPC#解决了传统C#的几个痛点,其中作者本人最关心的不过于没有GC,没有class types以及没有boxing。GC影响性能这是每一个有GC语言的痛点,class type也就是managed objects造成了巨大的cache misses,而boxing同样十分影响性能。

Optimizations with burst

img_v2_08793326-e480-4d6d-9187-9a7c838894eg.jpg

img_v2_b3b93092-c954-4f12-8549-4cfdf940e62g.jpg

img_v2_56ec6145-b5fb-481d-99af-88d7ef1d2d5g.jpg

简单来说,burst的优化体现在.NET IL转换为LLVM IR的阶段,这一阶段毫无疑问是在LLVM中完成,具体如何优化可以参考LLVM的官方文档——optimizingLLVMIR

其大致的工作流程如:

    1                2              3             4
c# -----> .NET IL  -----> LLVM IR -----> C++ -----> native assembly

1. 第一个阶段是由.NET或者mono的编译器,将C#代码编译为.NET IL指令
2. 第二个阶段是由Burst,或者说是LLVM,将.NET IL指令转译为LLVM IR指令,在这一阶段,LLVM会对IR进行优化
3. 将LLVM IR通过IL2CPP转换为C++代码
4. 通过C++编译器将C++代码编译为对应平台的汇编指令

观察这一过程的最佳方法是unity提供的Burst Inspector工具。

在通过Burst优化之后,同一份代码生成的不同汇编如下:

img_v2_352fa19d-a217-4e97-98dd-e003815ab8fg.jpg

可以看到,burst最大程度用了AVX寄存器,用最少的汇编指令做了和mono一样的工作。

Optimizations with Unity.Mathematics

img_v2_8deb4435-5b86-440f-8719-e5d898fc950g.jpg

简单来说,就是使用Unity.Mathematics之后,编译器会尽可能地使用SIMD指令,以此来优化代码。

优化的大致方法如下:

img_v2_2630d215-2774-4d1c-975d-4a93bd5a9dcg.jpg

Optimizations Memory(no) aliasing

关于aliasing的定义点击: aliasing。 简单来说,aliasing就是一块在内存的数据被命名为不同的符号,这样的结果导致编译器很有可能误认为这些符号具有不同的地址,最终导致一些没有必要的指令被生成出来。

aliasing会造成的一些问题: img_v2_dd9d843c-431b-4d42-81c4-11f92a3bfc3g.jpg

img_v2_5c5647fc-8b1d-4165-8f20-03d90016356g.jpg

Unity的解决方案: img_v2_c6776be3-fab0-4b43-b7b7-86842f71668g.jpg

一个优化案例: img_v2_10fefce2-7e76-4243-a736-74f355453e1g.jpg

简单来说,memory aliasing技术也是一种指令优化的手段,它取决于代码的上下文中内存指针的使用以及执行顺序,类似于上图,就减少了一个mov指令。

上图只是aliasing的一种情况,具体造成aliasing问题的情况还有多种。

example

一些unity版本并没有自带Burst,因此需要手动安装。 点击Editor的 Windows->Package Manager:

image.png

点击Add package from git URL添加,输入:com.unity.jobs. 重启unity Editor之后就可以看见Jobs了。

image.png

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 Inspector,可以看到Jobs对应的IL指令:

image.png

Reference

About Burst