Enhanced Aliasing with Burst

The Unity Burst Compiler transforms your C# code into highly optimized machine code. Since the first stable release of Burst Compiler a year ago, we have been working to improve the quality, experience, and robustness of the compiler. As we’ve released a major new version, Burst 1.3, we would like to take this opportunity to give you more insights about why we are excited about a key performance focused feature – our new enhanced aliasing support.

The new compiler intrinsics Unity.Burst.CompilerServices.Aliasing.ExpectAliased and Unity.Burst.CompilerServices.Aliasing.ExpectNotAliased allow users to gain deep insight into how the compiler understands the code they write. These new intrinsics are combined with extended support for the [Unity.Burst.NoAlias] attribute, we’ve given our users a new superpower in the quest for performance.

Takeaways

In this blog post we will explain the concept of aliasing, how to use the [NoAlias] attribute to explain how the memory in your data structures alias, and how to use our new aliasing compiler intrinsics to be certain the compiler understands your code the way you do.

Aliasing

Aliasing is when two pointers to data happen to be pointing to the same memory allocation.

int Foo(ref int a, ref int b) { b = 13; a = 42; return b; }

1

2

3

4

5

6

int Foo(ref int a, ref int b)

{

    b = 13;

    a = 42;

    return b;

}

The above is a classic performance related aliasing problem – the compiler without any external information cannot assume whether a aliases with b, and so produces the following nonoptimal assembly:

mov dword ptr [rdx], 13 mov dword ptr [rcx], 42 mov eax, dword ptr [rdx] ret

mov     dword ptr [rdx], 13

mov     dword ptr [rcx], 42

mov     eax, dword ptr [rdx]

ret

As can be seen it:

Stores 13 into b. Stores 42 into a. Reloads the value from b to return it.

It has to reload b because the compiler does not know whether a and b are backed by the same memory or not – if they were backed by the same memory then b will contain the value 42, if they were not it would contain the value 13.

A More Complex Example

Let’s look at the following simple job:

[BurstCompile] private struct CopyJob : IJob { [ReadOnly] public NativeArray Input; [WriteOnly] public NativeArray Output; public void Execute() { for (int i = 0; i < Input.Length; i++) { Output[i] = Input[i]; } } }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

[BurstCompile]

private struct CopyJob : IJob

{

    [ReadOnly]

    public NativeArray Input;

    [WriteOnly]

    public NativeArray Output;

    public void Execute()

    {

        for (int i = 0; i < Input.Length; i++)

        {

            Output[i] = Input[i];

        }

    }

}

The above job is simply copying from one buffer to another. If Input and Output do not alias above, EG. none of the memory locations backing them do not overlap, then the output from this job is:

If a compiler is aware that these two buffers do not

Continue reading

This post was originally published on this site