Unity Burst
What's Burst
首先需要了解的是,unity burst是一种基于llvm的编译器优化解决方案,在使用了burst技术之后,从.NET的IL到Native代码的流程大致如下:
简单来说,burst技术就是在由c#代码生成的.NET IL之后,以及在il2cpp执行之前的时间段中,对代码进行优化。
不过为什么不是在il2cpp层面对此进行的优化?对于这个问题,官方也相对给出了解释如下:
简单来说,就是IL2CPP的设计初衷并不是极致地追求性能,而是最大程度地为了平台地兼容性(IOS)。
How Burst work?
因为Burst代码还是在c#层面,所以需要在c#语言层面对此提供相应的支持,unity官方将这一技术称之为 IL/C# Subset, 这种Subset也被称作HPC#(High Performance C#)。
之所以HPC#中带有(HP),是因为相较于普通的c#,HPC具有以下几个特点:
- No class types
- No boxing
- No GC allocation
- No exceptions for control flow
可以看到,HPC#解决了传统C#的几个痛点,其中作者本人最关心的不过于没有GC,没有class types以及没有boxing。GC影响性能这是每一个有GC语言的痛点,class type也就是managed objects造成了巨大的cache misses,而boxing同样十分影响性能。
Optimizations with burst
简单来说,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优化之后,同一份代码生成的不同汇编如下:
可以看到,burst最大程度用了AVX寄存器,用最少的汇编指令做了和mono一样的工作。
Optimizations with Unity.Mathematics
简单来说,就是使用Unity.Mathematics之后,编译器会尽可能地使用SIMD指令,以此来优化代码。
优化的大致方法如下:
Optimizations Memory(no) aliasing
关于aliasing的定义点击: aliasing。 简单来说,aliasing就是一块在内存的数据被命名为不同的符号,这样的结果导致编译器很有可能误认为这些符号具有不同的地址,最终导致一些没有必要的指令被生成出来。
aliasing会造成的一些问题:
Unity的解决方案:
一个优化案例:
简单来说,memory aliasing技术也是一种指令优化的手段,它取决于代码的上下文中内存指针的使用以及执行顺序,类似于上图,就减少了一个mov
指令。
上图只是aliasing的一种情况,具体造成aliasing问题的情况还有多种。
example
一些unity版本并没有自带Burst,因此需要手动安装。 点击Editor的 Windows->Package Manager:
点击Add package from git URL
添加,输入:com.unity.jobs
.
重启unity Editor之后就可以看见Jobs了。
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指令: