Search Unity

Our High Performance C# (HPC#) compiler technology, Burst, just keeps getting better. That’s why we want to unpack some major improvements made in the most recent version, Burst 1.5. In this post, we’ll take you through the headline features and their benefits, so you can make the most of Burst in your projects.

Arm Neon hardware intrinsics

In collaboration with our partners at Arm, we’ve added Arm Neon hardware intrinsics to Burst 1.5. These allow you to target the specific hardware instructions available on Arm platforms, including the amazing vector technology, Neon, in all its glory.

Arm Neon intrinsics were first introduced as an experimental feature in Burst 1.4, and are now fully supported in Burst 1.5. Burst currently includes all Armv8-A intrinsics, with Armv8.1-RDMA, Armv8.2-DotProd and Armv8.2-Crypto intrinsics as experimental features of Burst 1.5 that will be fully supported in the next Burst version.

Arm Neon intrinsics make use of the v128 type, familiar from Intel intrinsics, as well as the v64 type. These types comprise bags of 128 or 64 bits, respectively. It’s up to you to verify and correctly treat the vector element type and count. After all, these vectors are a representation of the actual SIMD register on the CPU.

See a simple usage example:

Keep in mind that the IsNeonSupported value is evaluated at compile time based on your target CPU, so it doesn’t affect the runtime performance. If you want to provide multiple intrinsics implementations for Arm and Intel target CPUs, you should include more of the IsXXXSupported blocks in your code.

It is important to consider that Neon intrinsics are only supported on Armv8-A hardware (64-bit). On Armv7-A (32-bit), IsNeonSupported will always be false. If you target older 32-bit Arm devices, you can still rely on Burst to optimize your managed code automatically, without using Neon intrinsics directly.

We’ll follow up on Arm intrinsics and share further details on Neon intrinsics in a subsequent blog.

Hardware intrinsics target advanced users who want to harness the absolute maximum performance from the compiler, and seek to fine-tune their code to squeeze down a few more CPU cycles. If you take on this challenge, we’d love to hear your feedback.

Direct Call

A prominent new feature in Burst 1.5 is what we refer to as Direct Call. With Burst, we began focusing on jobs to accelerate tasks that run on Unity’s job system with our HPC# compiler. We then added function pointers, so you can manage and call into bits of Burst code from just about anywhere:

While this greatly enhanced Burst’s performance, we realized that this mechanism was clunky to use from places outside the job system. With Direct Call, Burst carries out this transformation for you, meaning that you just have to complete the following:

The code proceeds to run through Burst. Note that Direct Call methods only work this way (as shown above) when called from the main thread.

New optimization superpowers

In Burst 1.5, we’ve added ample new and interesting functionalities to give you some extra optimization superpowers.

Hint.Likely, Hint.Unlikely and Hint.Assume

One key request that has continued to come up focuses on the use of intrinsics to inform the compiler whether something is likely or unlikely to happen. In Burst 1.5, we’ve added two new intrinsics to Unity.Burst.CompilerServices – Likely and Unlikely:

These intrinsics enable you to tell the compiler whether some boolean condition (like the condition of an “if” branch) is either likely or unlikely to be hit. This allows the compiler to optimize the resulting code.

We’ve also added an Assume intrinsic:

This intrinsic informs the compiler of certain trends that will always occur. For instance, you can use Assume to tell the compiler that a pointer is never null, an index is never negative, a value is never NaN, and so forth. Be careful though – the compiler won’t check if your Assume is actually valid, so please ensure that your assumptions are actually true.

IsConstantExpression

We’ve also added an intrinsic to query whether an expression evaluates to a constant expression at compile time:

This query can be used as shown above, to ensure that some value is constant. Otherwise, it can be used in algorithms with faster paths, if, for example, something is definitely not NaN or null.

[SkipLocalsInit]

In C#, all local variables are zero initialized by default. Sometimes developers want to skip the cost of doing this zero initialization, so we added an attribute [SkipLocalsInit] to do just that. Simply apply this attribute to any function that you don’t want to have the zero initialization happen on. This mirrors .NET 5’s SkipLocalsInitAttribute functionality, but brings it to Burst sooner.

Miscellaneous improvements

Check out these smaller but equally awesome improvements in 1.5, in no particular order:

  • Burst now supports ValueTuple structures (int, float) within Bursted code – so long as types don’t stray across entry-point boundaries. For example, you can’t store them in a job struct or return them from a function pointer.
  • We added Bmi1 and Bmi2 x86 intrinsics to Burst 1.5 – gating them on AVX2 support. Any CPU that has AVX2 support can now make use of these incredible bit manipulation instructions directly in their code.
  • In Unity 2020.2 or newer versions, you can now call new ProfilerMarker(“MarkerName”) from Bursted code.
  • We also re-enabled the Burst warning BC1370, exclusively in player builds. This warning tells you where throws appear in a function unguarded by [Conditional(“ENABLE_UNITY_COLLECTIONS_CHECKS”)] – which isn’t supported in player builds.
  • Finally, there is a whole slew of performance improvements surrounding the use of LLVM 11 as our default code generator, along with optimizations for stackalloc hoisting, dead loop removal, compile time improvements and much more.

One last dance for 2018.4

Burst 1.5 is the last version to support Unity 2018.4. Our next version will have a minimum requirement of Unity 2019.4.

Start using Burst

Burst is a core part of our technology stack that you can start using today. It is a stable and verified package, already employed in thousands of projects, and counting. While our DOTS technology stack leverages Burst to provide highly optimized code, Burst also serves as a stand-alone package outside of DOTS.

It supports all the major desktop, console and mobile platforms, and works with Unity 2018.4 or newer.

If you have any thoughts, questions, or would just like to let us know what you are doing with Burst, then please feel free to leave us a message on the Burst forum.

5 replies on “Bursting into 2021 with Burst 1.5”

This is fantastic and the optimizations and compile time speed ups has been a life saver more or less.

Also the fact that we can almost write assembly inside burst whenever needed is fantastic.

Yes it might not be useful for some users but this is true about every feature. Anyone who is selfish enough to suggest that resources should be focused on the area that is useful to him/her suffers from seeing oneself as too important. I hope that these things don’t affect Unity’s sharing of these posts and feature development more than it is affected it now regarding the hiding of packages.

Keep up the good work it is being highly appreciated by many as you should know pretty well.

Unlike before when something in the project needs to be very fast, instead of thinking about God damn C plugins or not using unity or not doing it at all, we simply use burst, Do you need to write fast player interest management, AI … just use burst for that part of the code, copy data to native arrays and burst the code which works on it, it could be player positions, their instance id (for finding them back and attaching the modified data to the GameObject again), the state that needs to be modified and anything else needed. the data which needs calculation is rarely big/complex if you think it through. the copy might seem to slow you down and indeed it can if used incorrectly but in general it helps if you have enough calculations.

These are some really detailed optimisation options!
I wonder how often burst and arm optimisations will make a big difference in the runtime speed of code though – it seems you create a new kind of C/C# variant (with pointers!) with Burst, for very optimised code that will not be comfortable for everyone. Can anyone of it make its way back to C# through special function calls?

It’s not really a C# variant. Burst is allowing you to program with a subset of C# that doesn’t use managed pointers. You don’t need to use special intrinsics like the one shown in this blog post to leverage Burst. The vast majority of the time, Burst compiler will be able to optimize your code enough that you will never need to use such intrinsics or manipulate pointers. That being said, we have some users that are asking for more control over low-level code gen, and these intrinsics are allowing such cases.

Leave a Reply