postgresql 别名_突发增强的别名

postgresql 别名

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.

统一的突发编译器 将你的C#代码转换为高度优化的机器代码。 自一年前Burst Compiler的第一个稳定版本以来,我们一直在努力提高编译器的质量,体验和健壮性。 当我们发布了一个主要的新版本Burst 1.3时,我们希望借此机会为您提供更多的见解,以使我们对为何对一项关键的性能关注功能感到兴奋-我们的新增强混叠支持感到满意。

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.

新的编译器内部函数Unity.Burst.CompilerServices.Aliasing.ExpectAliased和Unity.Burst.CompilerServices.Aliasing.ExpectNotAliased允许用户深入了解编译器如何理解他们编写的代码。 这些新的内在函数与对[Unity.Burst.NoAlias]属性的扩展支持相结合,我们为用户提供了追求性能的新超能力。

外卖 (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.

在这篇博客中,我们将解释别名的概念,如何使用[NoAlias]属性来解释数据结构别名中的内存,以及如何使用我们的新别名编译器内部函数来确保编译器以某种方式理解您的代码你做。

混叠 (Aliasing)

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

别名是指两个指向数据的指针恰好指向同一内存分配。

1

2
3
4
5
6
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:

上面是一个典型的与性能有关的别名问题–没有任何外部信息的编译器无法假定别名是否为b,因此会产生以下非最佳汇编:

1

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

1

2
3
4
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.

    将13存储到b中。

  • Stores 42 into a.

    将42存储到a中。

  • Reloads the value from b to return it.

    从b重新加载值以返回它。

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.

它重新加载B,因为编译器 不知道 是否a和b是由同一存储器或不支持的-如果它们 由相同的内存支持,那么B将包含值42,如果 他们不 将包含值13 。

一个更复杂的例子 (A More Complex Example)

Let’s look at the following simple job:

让我们看一下以下简单的工作:

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<float> Input;
    [WriteOnly]
    public NativeArray<float> 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 < float > Input ;
     [ WriteOnly ]
     public NativeArray < float > 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:

上面的工作只是从一个缓冲区复制到另一个缓冲区。 如果输入和输出在上面没有别名,例如EG。 没有任何支持它们的内存位置不重叠,则此作业的输出为:

If a compiler is aware that these two buffers do not alias, like Burst is with the above code example, then the compiler can vectorize the code such that it can copy N things instead of one at at time:

如果编译器知道这两个缓冲区 没有别名 (如上面的代码示例中的Burst所示),则编译器可以对代码进行矢量化处理,使其可以一次复制N个内容而不是一个内容:

Let’s look at what would happen if Input and Output happened to alias above. Firstly, the safety system will catch these common kinds of cases and provide user feedback if a mistake has been made. But let’s assume you’ve turned safety checks off, what would happen?

让我们看看如果输入和输出碰巧出现在上面的别名会发生什么。 首先,如果发生错误,安全系统将捕获这些常见情况,并向用户提供反馈。 但是,假设您已关闭安全检查,会发生什么?

As you can see, because the memory locations slightly overlap, the value a from the Input ends up propagated across the entirety of Output. Let’s assume that the compiler also vectorized this example because it wrongly thought the memory locations did not alias, what would happen now?

如您所见,由于内存位置略有重叠,因此Input的值a最终会传播到整个Output上。 假设编译器也对这个示例进行了矢量化处理,因为它 错误地 认为内存位置没有别名,现在会发生什么?

Very bad things happen – the Output will not contain the data you expected.

发生非常糟糕的事情 –输出将不包含您期望的数据。

Aliasing limits the Burst compilers ability to optimize code. It has an especially hard toll on vectorization – if the compiler thinks that any of the variables being used in the loop can alias, it generally cannot safely vectorize the loop. In Burst 1.3.0 and later, with our extended and improved aliasing support, we have vastly improved our performance story around aliasing.

别名限制了Burst编译器优化代码的能力。 它对向量化的影响特别大-如果编译器认为循环中使用的任何变量都可以别名,则通常 无法 安全地对循环进行向量化。 在Burst 1.3.0和更高版本中,通过扩展和改进的别名支持,我们极大地改善了有关别名的性能。

引入[NoAlias]属性 (Introducing the [NoAlias] Attribute)

In Burst 1.3.0 we’ve extended where the [NoAlias] attribute can be placed to four places:

在Burst 1.3.0中,我们扩展了[NoAlias]属性可以放置到四个位置的位置:

  • On a function parameter it signifies that the parameter does not alias with any other parameter to the function, or with the ‘this’ pointer.

    在函数参数上,它表示该参数不与函数的任何其他参数或“ this”指针混用。

  • On a field it signifies that the field does not alias with any other field of the struct.

    在字段上,表示该字段不与结构的其他任何字段混叠。

  • On a struct itself it signifies that the address of the struct cannot appear within the struct itself.

    在结构本身上,它表示该结构的地址不能出现在该结构本身内。

  • On a function return value it signifies that the returned pointer does not alias with any other pointer ever returned from the same function.

    在函数返回值上,它表示返回的指针不会与从同一函数返回的任何其他指针混叠。

In cases of fields and parameters, if the field type or parameter type is a struct, “does not alias with X” means that all pointers that can be found through any of the fields (even indirectly) of that struct are guaranteed not to alias with X.

对于字段和参数,如果字段类型或参数类型是结构,则 “不使用X别名” 表示可以保证通过该结构的任何字段(甚至间接)可以找到的所有指针都没有别名与X。

In cases of parameters, note that a [NoAlias] attribute on a parameter guarantees it does not alias with this, which often is a job struct, which contains all data for the struct. In Entities.ForEach() scenarios, this will contain all the variables that were captured by the lambda.

在使用参数的情况下,请注意,参数上的[NoAlias]属性可确保它不以此为别名,该属性通常是作业结构,其中包含该结构的所有数据。 在Entities.ForEach()方案中,它将包含lambda捕获的所有变量。

We will now go through an example of each of these uses in turn.

现在,我们将依次介绍这些用法的示例。

NoAlias功能参数 (NoAlias Function Parameter)

If we look again at the example with Foo above, we can now add a [NoAlias] attribute and see what we get:

如果我们再看一下上面带有Foo的示例,我们现在可以添加[NoAlias]属性并查看得到的结果:

1

2
3
4
5
6
int Foo([NoAlias] ref int a, ref int b)
{
    b = 13;
    a = 42;
    return b;
}

1

2
3
4
5
6
int Foo ( [ NoAlias ] ref int a , ref int b )
{
     b = 13 ;
     a = 42 ;
     return b ;
}

Which turns into:

变成:

1

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

1

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

Notice that the load from ‘b’ has been replaced with moving the constant 13 into the return register.

注意,通过将常数13移到返回寄存器中已替换了来自“ b”的负载。

NoAlias结构域 (NoAlias Struct Field)

Let’s take the same example from above but apply it to a struct instead:

让我们从上面以相同的示例为例,但将其应用于结构:

1

2
3
4
5
6
7
8
9
10
11
12
struct Bar
{
    public NativeArray<int> a;
    public NativeArray<float> b;
}
int Foo(ref Bar bar)
{
    bar.b[0] = 42.0f;
    bar.a[0] = 13;
    return (int)bar.b[0];
}

1

2
3
4
5
6
7
8
9
10
11
12
struct Bar
{
     public NativeArray < int > a ;
     public NativeArray < float > b ;
}
int Foo ( ref Bar bar )
{
     bar . b [ 0 ] = 42.0f ;
     bar . a [ 0 ] = 13 ;
     return ( int ) bar . b [ 0 ] ;
}

The above produces the following assembly:

上面产生了以下程序集:

1

2
3
4
5
6
mov       rax, qword ptr [rcx + 16]
mov       dword ptr [rax], 1109917696
mov       rcx, qword ptr [rcx]
mov       dword ptr [rcx], 13
cvttss2si eax, dword ptr [rax]
ret

1

2
3
4
5
6
mov       rax , qword ptr [ rcx + 16 ]
mov       dword ptr [ rax ] , 1109917696
mov       rcx , qword ptr [ rcx ]
mov       dword ptr [ rcx ] , 13
cvttss2si eax , dword ptr [ rax ]
ret

Which when parsed into our speech translates to:

解析为我们的语音后,它会转换为:

  • Loads the address of the data in ‘b’ into rax.

    将“ b”中的数据地址加载到rax中。

  • Stores 42 into it (1109917696 is 0x‭42280000‬ which is 42.0f).

    将42存储到其中(1109917696是0x‭42280000‬是42.0f)。

  • Loads the address of the data in ‘a’ into rcx.

    将“ a”中的数据地址加载到rcx中。

  • Stores 13 into it.

    将13存储到其中。

  • Reloads the data in ‘b’ and converts it to an integer for returning.

    将数据重新加载到“ b”中,并将其转换为整数以返回。

Let’s assume that you as the user know that the two NativeArray’s are not backed by the same memory, you could:

让我们假设您作为用户知道两个NativeArray不在同一内存中,则可以:

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Bar
{
    [NoAlias]
    public NativeArray<int> a;
    [NoAlias]
    public NativeArray<float> b;
}
int Foo(ref Bar bar)
{
    bar.b[0] = 42.0f;
    bar.a[0] = 13;
    return (int)bar.b[0];
}

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Bar
{
     [ NoAlias ]
     public NativeArray < int > a ;
     [ NoAlias ]
     public NativeArray < float > b ;
}
int Foo ( ref Bar bar )
{
     bar . b [ 0 ] = 42.0f ;
     bar . a [ 0 ] = 13 ;
     return ( int ) bar . b [ 0 ] ;
}

By attributing both a and b with [NoAlias] we have told the compiler that they definitely do not alias with each other within the struct, which produces the following assembly:

通过使用[NoAlias]来同时给a和b赋值,我们告诉编译器,它们在结构中绝对不会彼此别名,从而产生以下程序集:

1

2
3
4
5
6
mov     rax, qword ptr [rcx + 16]
mov     dword ptr [rax], 1109917696
mov     rax, qword ptr [rcx]
mov     dword ptr [rax], 13
mov     eax, 42
ret

1

2
3
4
5
6
mov     rax , qword ptr [ rcx + 16 ]
mov     dword ptr [ rax ] , 1109917696
mov     rax , qword ptr [ rcx ]
mov     dword ptr [ rax ] , 13
mov     eax , 42
ret

Notice that the compiler can now just return the integer constant 42!

注意,编译器现在可以返回整数常量42!

结构上的NoAlias (NoAlias on a Struct)

Nearly all structs you will create as a user will be able to have the assumption that the pointer to the struct does not appear within the struct itself. Let’s take a look at a classic example where this is not true:

您将以用户身份创建的几乎所有结构都可以假定该结构的指针不会出现在结构本身中。 让我们看一个 正确 的经典示例 :

1

2
3
4
5
6
7
8
9
10
unsafe struct CircularList
{
    public CircularList* next;
    public CircularList()
    {
        // The 'empty' list just points to itself.
        next = this;
    }
}

1

2
3
4
5
6
7
8
9
10
unsafe struct CircularList
{
     public CircularList * next ;
     public CircularList ( )
     {
         // The 'empty' list just points to itself.
         next = this ;
     }
}

Lists are one of the few structures where it is normal to have the pointer to the struct accessible from somewhere within the struct itself.

列表是通常可以从结构本身内部的某个位置访问结构指针的少数结构之一。

Now onto a more concrete example of where [NoAlias] on a struct can help:

现在来看一个更具体的示例,该示例在结构上的[NoAlias]可以提供帮助:

1

2
3
4
5
6
7
8
9
10
11
unsafe struct Bar
{
    public int i;
    public void* p;
}
float Foo(ref Bar bar)
{
    *(int*)bar.p = 42;
    return ((float*)bar.p)[bar.i];
}

1

2
3
4
5
6
7
8
9
10
11
unsafe struct Bar
{
     public int i ;
     public void * p ;
}
float Foo ( ref Bar bar )
{
     * ( int * ) bar . p = 42 ;
     return ( ( float * ) bar . p ) [ bar . i ] ;
}

Which produces the following assembly:

产生以下程序集:

1

2
3
4
5
6
mov     rax, qword ptr [rcx + 8]
mov     dword ptr [rax], 42
mov     rax, qword ptr [rcx + 8]
mov     ecx, dword ptr [rcx]
movss   xmm0, dword ptr [rax + 4*rcx]
ret

1

2
3
4
5
6
mov     rax , qword ptr [ rcx + 8 ]
mov     dword ptr [ rax ] , 42
mov     rax , qword ptr [ rcx + 8 ]
mov     ecx , dword ptr [ rcx ]
movss   xmm0 , dword ptr [ rax + 4 * rcx ]
ret

As can be seen it:

可以看出:

  • Loads ‘p’ into rax.

    将“ p”加载到rax中。

  • Stores 42 into ‘p’.

    将42存储到“ p”中。

  • Loads ‘p’ into rax again!

    再次将“ p”加载到rax中!

  • Loads ‘i’ into ecx.

    将“ i”加载到ecx中。

  • Returns the index into ‘p’ by ‘i’.

    通过“ i”将索引返回到“ p”。

Notice that it loaded ‘p’ twice – why? The reason is that the compiler does not know whether ‘p’ points to the address of the struct bar itself – so once it has stored 42 into ‘p’, it has to reload the address of ‘p’ from ‘bar’, just in case. A wasted load!

请注意,它两次加载了“ p”-为什么? 原因是编译器不知道'p'是否指向结构条本身的地址-因此,一旦将42存储到'p'中,它就必须从'bar'重新加载'p'的地址,如果。 浪费的负载!

Let’s add [NoAlias] now:

现在添加[NoAlias]:

1

2
3
4
5
6
7
8
9
10
11
12
[NoAlias]
unsafe struct Bar
{
    public int i;
    public void* p;
}
float Foo(ref Bar bar)
{
    *(int*)bar.p = 42;
    return ((float*)bar.p)[bar.i];
}

1

2
3
4
5
6
7
8
9
10
11
12
[ NoAlias ]
unsafe struct Bar
{
     public int i ;
     public void * p ;
}
float Foo ( ref Bar bar )
{
     * ( int * ) bar . p = 42 ;
     return ( ( float * ) bar . p ) [ bar . i ] ;
}

Which produces the following assembly:

产生以下程序集:

1

2
3
4
5
mov     rax, qword ptr [rcx + 8]
mov     dword ptr [rax], 42
mov     ecx, dword ptr [rcx]
movss   xmm0, dword ptr [rax + 4*rcx]
ret

1

2
3
4
5
mov     rax , qword ptr [ rcx + 8 ]
mov     dword ptr [ rax ] , 42
mov     ecx , dword ptr [ rcx ]
movss   xmm0 , dword ptr [ rax + 4 * rcx ]
ret

Notice that it only loaded the address of ‘p’ once, because we’ve told the compiler that ‘p’ cannot be the pointer to ‘bar’.

注意,它只加载了“ p”的地址一次,因为我们已经告诉编译器“ p”不能是指向“ bar”的指针。

NoAlias函数返回 (NoAlias Function Return)

Some functions can only return a unique pointer. For instance, malloc will only ever give you a unique pointer. For these cases [return:NoAlias] can provide the compiler with some useful information.

某些函数只能返回唯一的指针。 例如,malloc只会给您唯一的指针。 对于这些情况,[return:NoAlias]可以为编译器提供一些有用的信息。

Let’s take an example using a bump allocator backed with a stack allocation:

让我们举一个使用带有堆栈分配支持的凹凸分配器的示例:

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Only ever returns a unique address into the stackalloc'ed memory.
// We've made this no-inline as the compiler will always try and inline
// small functions like these, which would defeat the purpose of this
// example!
[MethodImpl(MethodImplOptions.NoInlining)]
unsafe int* BumpAlloc(int* alloca)
{
    int location = alloca[0]++;
    return alloca + location;
}
unsafe int Func()
{
    int* alloca = stackalloc int[128];
    // Store our size at the start of the alloca.
    alloca[0] = 1;
    int* ptr1 = BumpAlloc(alloca);
    int* ptr2 = BumpAlloc(alloca);
    *ptr1 = 42;
    *ptr2 = 13;
    return *ptr1;
}

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Only ever returns a unique address into the stackalloc'ed memory.
// We've made this no-inline as the compiler will always try and inline
// small functions like these, which would defeat the purpose of this
// example!
[ MethodImpl ( MethodImplOptions . NoInlining ) ]
unsafe int * BumpAlloc ( int * alloca )
{
     int location = alloca [ 0 ] ++ ;
     return alloca + location ;
}
unsafe int Func ( )
{
     int * alloca = stackalloc int [ 128 ] ;
     // Store our size at the start of the alloca.
     alloca [ 0 ] = 1 ;
     int * ptr1 = BumpAlloc ( alloca ) ;
     int * ptr2 = BumpAlloc ( alloca ) ;
     * ptr1 = 42 ;
     * ptr2 = 13 ;
     return * ptr1 ;
}

Which produces the following assembly:

产生以下程序集:

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
push    rsi
push    rdi
push    rbx
sub     rsp, 544
lea     rcx, [rsp + 36]
movabs  rax, offset memset
mov     r8d, 508
xor     edx, edx
call    rax
mov     dword ptr [rsp + 32], 1
movabs  rbx, offset "BumpAlloc(int* alloca)"
lea     rsi, [rsp + 32]
mov     rcx, rsi
call    rbx
mov     rdi, rax
mov     rcx, rsi
call    rbx
mov     dword ptr [rdi], 42
mov     dword ptr [rax], 13
mov     eax, dword ptr [rdi]
add     rsp, 544
pop     rbx
pop     rdi
pop     rsi
ret

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
push     rsi
push     rdi
push     rbx
sub     rsp , 544
lea     rcx , [ rsp + 36 ]
movabs   rax , offset memset
mov     r8d , 508
xor     edx , edx
call     rax
mov     dword ptr [ rsp + 32 ] , 1
movabs   rbx , offset "BumpAlloc(int* alloca)"
lea     rsi , [ rsp + 32 ]
mov     rcx , rsi
call     rbx
mov     rdi , rax
mov     rcx , rsi
call     rbx
mov     dword ptr [ rdi ] , 42
mov     dword ptr [ rax ] , 13
mov     eax , dword ptr [ rdi ]
add     rsp , 544
pop     rbx
pop     rdi
pop     rsi
ret

It’s quite a lot of assembly, but the key bit is that it:

这是很多组装,但是关键是:

  • Has ‘ptr1’ in rdi.

    在rdi中有'ptr1'。

  • Has ‘ptr2’ in rax.

    rax中有“ ptr2”。

  • Stores 42 into ‘ptr1’.

    将42存储到“ ptr1”中。

  • Stores 13 into ‘ptr2’.

    将13存储到“ ptr2”中。

  • Loads ‘ptr1’ again to return it.

    再次加载“ ptr1”以返回它。

Let’s now add our [return: NoAlias] attribute:

现在让我们添加[return:NoAlias]属性:

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// We've made this no-inline as the compiler will always try and inline
// small functions like these, which would defeat the purpose of this
// example!
[MethodImpl(MethodImplOptions.NoInlining)]
[return: NoAlias]
unsafe int* BumpAlloc(int* alloca)
{
    int location = alloca[0]++;
    return alloca + location;
}
unsafe int Func()
{
    int* alloca = stackalloc int[128];
    // Store our size at the start of the alloca.
    alloca[0] = 1;
    int* ptr1 = BumpAlloc(alloca);
    int* ptr2 = BumpAlloc(alloca);
    *ptr1 = 42;
    *ptr2 = 13;
    return *ptr1;
}

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// We've made this no-inline as the compiler will always try and inline
// small functions like these, which would defeat the purpose of this
// example!
[ MethodImpl ( MethodImplOptions . NoInlining ) ]
[ return : NoAlias ]
unsafe int * BumpAlloc ( int * alloca )
{
     int location = alloca [ 0 ] ++ ;
     return alloca + location ;
}
unsafe int Func ( )
{
     int * alloca = stackalloc int [ 128 ] ;
     // Store our size at the start of the alloca.
     alloca [ 0 ] = 1 ;
     int * ptr1 = BumpAlloc ( alloca ) ;
     int * ptr2 = BumpAlloc ( alloca ) ;
     * ptr1 = 42 ;
     * ptr2 = 13 ;
     return * ptr1 ;
}

Which produces:

产生:

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
push    rsi
push    rdi
push    rbx
sub     rsp, 544
lea     rcx, [rsp + 36]
movabs  rax, offset memset
mov     r8d, 508
xor     edx, edx
call    rax
mov     dword ptr [rsp + 32], 1
movabs  rbx, offset "BumpAlloc(int* alloca)"
lea     rsi, [rsp + 32]
mov     rcx, rsi
call    rbx
mov     rdi, rax
mov     rcx, rsi
call    rbx
mov     dword ptr [rdi], 42
mov     dword ptr [rax], 13
mov     eax, 42
add     rsp, 544
pop     rbx
pop     rdi
pop     rsi
ret

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
push     rsi
push     rdi
push     rbx
sub     rsp , 544
lea     rcx , [ rsp + 36 ]
movabs   rax , offset memset
mov     r8d , 508
xor     edx , edx
call     rax
mov     dword ptr [ rsp + 32 ] , 1
movabs   rbx , offset "BumpAlloc(int* alloca)"
lea     rsi , [ rsp + 32 ]
mov     rcx , rsi
call     rbx
mov     rdi , rax
mov     rcx , rsi
call     rbx
mov     dword ptr [ rdi ] , 42
mov     dword ptr [ rax ] , 13
mov     eax , 42
add     rsp , 544
pop     rbx
pop     rdi
pop     rsi
ret

And notice that the compiler doesn’t reload ‘ptr2’ but simply moves 42 into the return register.

并且请注意,编译器不会重新加载“ ptr2”,而只是将42移到返回寄存器中。

[return: NoAlias] should only ever be used on functions that are 100% guaranteed to produce a unique pointer, like with the bump-allocating example above, or with things like malloc. It is also important to note that the compiler aggressively inlines functions for performance considerations, and so small functions like the above will likely be inlined into their parents and produce the same result without the attribute (which is why we had to force no-inlining on the called function).

[return:NoAlias]只能用于 保证100%产生唯一指针的 函数, 例如上面的凹凸分配示例或malloc之类的函数。 还需要注意的是,出于性能考虑,编译器会积极地内联函数,因此像上述这样的小函数很可能会内联到其父级中,并在没有属性的情况下产生相同的结果(这就是为什么我们必须强制不内联被调用的函数)。

函数克隆可更好地推导别名 (Function Cloning for Better Aliasing Deduction)

For function calls where Burst knows about the aliasing between parameters to the function, Burst can infer the aliasing and propagate this onto the called function to allow for greater optimization opportunities. Let’s look at an example:

对于Burst知道函数参数之间的别名的函数调用,Burst可以推断别名并将其传播到被调用的函数上,以提供更大的优化机会。 让我们看一个例子:

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// We've made this no-inline as the compiler will always try and inline
// small functions like these, which would defeat the purpose of this
// example!
[MethodImpl(MethodImplOptions.NoInlining)]
int Bar(ref int a, ref int b)
{
    a = 42;
    b = 13;
    return a;
}
int Foo()
{
    var a = 53;
    var b = -2;
    return Bar(ref a, ref b);
}
Previously the code for Bar would be:
mov     dword ptr [rcx], 42
mov     dword ptr [rdx], 13
mov     eax, dword ptr [rcx]
ret

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// We've made this no-inline as the compiler will always try and inline
// small functions like these, which would defeat the purpose of this
// example!
[ MethodImpl ( MethodImplOptions . NoInlining ) ]
int Bar ( ref int a , ref int b )
{
     a = 42 ;
     b = 13 ;
     return a ;
}
int Foo ( )
{
     var a = 53 ;
     var b = - 2 ;
     return Bar ( ref a , ref b ) ;
}
Previously the code for Bar would be :
mov     dword ptr [ rcx ] , 42
mov     dword ptr [ rdx ] , 13
mov     eax , dword ptr [ rcx ]
ret

This is because within the Bar function, the compiler did not know the aliasing of ‘a’ and ‘b’. This is in line with what other compiler technologies will do with this code snippet.

这是因为在Bar函数中,编译器不知道'a'和'b'的别名。 这与其他编译器技术将对此代码段进行的处理一致。

Burst is smarter than this though, and through a process of function cloning Burst will create a copy of Bar where the aliasing properties of ‘a’ and ‘b’ are known not to alias, and replace the original call to Bar with a call to the copy. This results in the following assembly:

不过,Burst比这更聪明,并且通过函数克隆过程,Burst将创建Bar的副本,在该副本中,已知别名“ a”和“ b”不会被别名,并将对Bar的原始调用替换为对副本。 这将导致以下组装:

1

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

1

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

Which as we can see doesn’t perform the second load from ‘a’.

正如我们所看到的,它不会执行来自“ a”的第二次加载。

别名检查 (Aliasing Checks)

Since aliasing is so key to the compilers ability to optimize for performance, we’ve added some aliasing intrinsics:

由于别名是编译器优化性能的关键,因此我们添加了一些别名内在函数:

  • Unity.Burst.CompilerServices.Aliasing.ExpectAliased expects that the two pointers do alias, and generates a compiler error if not.

    Unity.Burst.CompilerServices.Aliasing.ExpectAliased期望这两个指针具有 别名, 否则会 产生编译器错误。

  • Unity.Burst.CompilerServices.Aliasing.ExpectNotAliased expects that the two pointers do not alias, and generates a compiler error if not.

    Unity.Burst.CompilerServices.Aliasing.ExpectNotAliased期望两个指针 互为 别名, 否则不 生成编译器错误。

An example:

一个例子:

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using static Unity.Burst.CompilerServices.Aliasing;
[BurstCompile]
private struct CopyJob : IJob
{
    [ReadOnly]
    public NativeArray<float> Input;
    [WriteOnly]
    public NativeArray<float> Output;
    public unsafe void Execute()
    {
        // NativeContainer attributed structs (like NativeArray) cannot alias with each other in a job struct!
        ExpectNotAliased(Input.getUnsafePtr(), Output.getUnsafePtr());
        // NativeContainer structs cannot appear in other NativeContainer structs.
        ExpectNotAliased(in Input, in Output);
        ExpectNotAliased(in Input, Input.getUnsafePtr());
        ExpectNotAliased(in Input, Output.getUnsafePtr());
        ExpectNotAliased(in Output, Input.getUnsafePtr());
        ExpectNotAliased(in Output, Output.getUnsafePtr());
        // But things definitely alias with themselves!
        ExpectAliased(in Input, in Input);
        ExpectAliased(Input.getUnsafePtr(), Input.getUnsafePtr());
        ExpectAliased(in Output, in Output);
        ExpectAliased(Output.getUnsafePtr(), Output.getUnsafePtr());
    }
}

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using static Unity . Burst . CompilerServices . Aliasing ;
[ BurstCompile ]
private struct CopyJob : IJob
{
     [ ReadOnly ]
     public NativeArray < float > Input ;
     [ WriteOnly ]
     public NativeArray < float > Output ;
     public unsafe void Execute ( )
     {
         // NativeContainer attributed structs (like NativeArray) cannot alias with each other in a job struct!
         ExpectNotAliased ( Input . getUnsafePtr ( ) , Output . getUnsafePtr ( ) ) ;
         // NativeContainer structs cannot appear in other NativeContainer structs.
         ExpectNotAliased ( in Input , in Output ) ;
         ExpectNotAliased ( in Input , Input . getUnsafePtr ( ) ) ;
         ExpectNotAliased ( in Input , Output . getUnsafePtr ( ) ) ;
         ExpectNotAliased ( in Output , Input . getUnsafePtr ( ) ) ;
         ExpectNotAliased ( in Output , Output . getUnsafePtr ( ) ) ;
         // But things definitely alias with themselves!
         ExpectAliased ( in Input , in Input ) ;
         ExpectAliased ( Input . getUnsafePtr ( ) , Input . getUnsafePtr ( ) ) ;
         ExpectAliased ( in Output , in Output ) ;
         ExpectAliased ( Output . getUnsafePtr ( ) , Output . getUnsafePtr ( ) ) ;
     }
}

These intrinsics allow you to be certain that the compiler has all the information that you as the user know. These are compile time checks. When the code you write to produce the arguments for the intrinsics do not have side effects, there is no runtime cost for these aliasing intrinsics. They are particularly useful when you have some code that is performance sensitive that you want to be sure that any later changes do not change the assumptions the compiler can make about aliasing. With Burst, and the control we have over the compiler, we can provide this sort of in-depth feedback from the compiler to our users to ensure your code remains as optimized as you intended.

这些内在函数使您可以确定编译器具有用户所知道的所有信息。 这些是编译时检查。 当您编写的用于为内部函数生成参数的代码没有副作用时,这些别名内部函数不会产生运行时开销。 当您具有一些对性能敏感的代码,并且希望确保以后进行的任何更改都不会改变编译器对别名的假设时,它们特别有用。 借助Burst,以及我们对编译器的控制,我们可以从编译器向用户提供这种深入的反馈,以确保您的代码保持预期的最佳状态。

作业系统别名 (Job System Aliasing)

The Unity Job System has some built-in assumptions it can make about aliasing. The rules are:

Unity Job System具有一些关于别名的内置假设。 规则是:

  1. Any struct with a [JobProducerType] (EG. anything like IJob, IJobParallelFor, etc) knows that any field of that struct that is a [NativeContainer] (EG. NativeArray, NativeSlice, etc) cannot alias with any other field that is also a [NativeContainer].

    任何具有[JobProducerType]结构(例如IJob,IJobParallelFor等),都知道该结构的任何字段都是[NativeContainer](例如EG。NativeArray,NativeSlice等)都不能与其他也是[NativeContainer]。

  2. The above is true except for fields that have the [NativeDisableContainerSafetyRestriction] attribute on them. For these fields, the user has explicitly told the Job System that this field can alias with any other field of the struct.

    具有[NativeDisableContainerSafetyRestriction]属性的字段 , 以上内容均适用 。 对于这些字段,用户已明确告诉作业系统该字段可以与结构的其他任何字段混叠。

  3. Any struct with a [NativeContainer] cannot have the ‘this’ pointer of that struct within the struct itself.

    具有[NativeContainer]的任何结构都不能在该结构本身内具有该结构的“ this”指针。

Ok all the formal definitions over, let’s look at some code to better explain the above rules:

确定所有正式定义,让我们看一些代码以更好地解释上述规则:

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[BurstCompile]
private struct JobSystemAliasingJob : IJobParallelFor
{
    public NativeArray<float> a;
    public NativeArray<float> b;
    [NativeDisableContainerSafetyRestriction]
    public NativeArray<float> c;
    public unsafe void Execute(int i)
    {
        // a & b do not alias because they are [NativeContainer]'s.
        ExpectNotAliased(a.GetUnsafePtr(), b.GetUnsafePtr());
        // But since c has [NativeDisableContainerSafetyRestriction] it can alias them.
        ExpectAliased(b.GetUnsafePtr(), c.GetUnsafePtr());
        ExpectAliased(a.GetUnsafePtr(), c.GetUnsafePtr());
        // No [NativeContainer]'s this pointer can appear within itself.
        ExpectNotAliased(in a, a.GetUnsafePtr());
        ExpectNotAliased(in b, b.GetUnsafePtr());
        ExpectNotAliased(in c, c.GetUnsafePtr());
    }
}

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[ BurstCompile ]
private struct JobSystemAliasingJob : IJobParallelFor
{
     public NativeArray < float > a ;
     public NativeArray < float > b ;
     [ NativeDisableContainerSafetyRestriction ]
     public NativeArray < float > c ;
     public unsafe void Execute ( int i )
     {
         // a & b do not alias because they are [NativeContainer]'s.
         ExpectNotAliased ( a . GetUnsafePtr ( ) , b . GetUnsafePtr ( ) ) ;
         // But since c has [NativeDisableContainerSafetyRestriction] it can alias them.
         ExpectAliased ( b . GetUnsafePtr ( ) , c . GetUnsafePtr ( ) ) ;
         ExpectAliased ( a . GetUnsafePtr ( ) , c . GetUnsafePtr ( ) ) ;
         // No [NativeContainer]'s this pointer can appear within itself.
         ExpectNotAliased ( in a , a . GetUnsafePtr ( ) ) ;
         ExpectNotAliased ( in b , b . GetUnsafePtr ( ) ) ;
         ExpectNotAliased ( in c , c . GetUnsafePtr ( ) ) ;
     }
}

Walking through the above aliasing checks:

通过上述别名检查:

  • a and b do not alias since they are both [NativeContainer]’s contained within a [JobProducerType] struct.

    a和b没有别名,因为它们都是[JobProducerType]结构中包含的[NativeContainer]。

  • But since c has the field attribute [NativeDisableContainerSafetyRestriction] it can alias with a or b.

    但是,由于c具有字段属性[NativeDisableContainerSafetyRestriction],因此可以使用a或b作为别名。

  • And the pointers to each of a, b, or c cannot appear within them (EG. in this case the data backing the NativeArray cannot be the data backing the contents of the array).

    并且指向a,b或c的指针不能出现在其中(例如,在这种情况下,支持NativeArray的数据不能是支持数组内容的数据)。

These built-in aliasing rules allow Burst to perform pretty darn good optimizations for most user code, allowing the performance by default that we strive for.

这些内置的别名规则允许Burst对大多数用户代码执行相当不错的优化,从而在默认情况下提供我们所追求的性能。

常见用例场景 (Common Use Case Scenario)

Many users will write code along the lines of BasicJob below:

许多用户将按照下面的BasicJob编写代码:

1

2
3
4
5
6
7
8
9
10
11
12
13
[BurstCompile]
private struct BasicJob : IJobParallelFor
{
    public NativeArray<float> a;
    public NativeArray<float> b;
    public NativeArray<float> c;
    public NativeArray<float> o;
    public void Execute(int i)
    {
        o[i] = a[i] * b[i] + c[i];
    }
}

1

2
3
4
5
6
7
8
9
10
11
12
13
[ BurstCompile ]
private struct BasicJob : IJobParallelFor
{
     public NativeArray < float > a ;
     public NativeArray < float > b ;
     public NativeArray < float > c ;
     public NativeArray < float > o ;
     public void Execute ( int i )
     {
         o [ i ] = a [ i ] * b [ i ] + c [ i ] ;
     }
}

The code is loading from three arrays, combining their results, and storing it to a fourth array. This kind of code is great for the compiler because it allows it to generate vectorized code, making the most of the powerful CPUs we all have in our mobiles and desktop computers today.

该代码从三个数组加载,合并它们的结果,并将其存储到第四个数组。 这种代码对于编译器非常有用,因为它允许它生成矢量化代码,从而充分利用了当今我们的手机和台式机中所有功能强大的CPU。

If we look at the Burst Inspector view of the above job:

如果我们看一下以上工作的Burst Inspector视图:

We can see the code is vectorized – the compiler has done a good job here! The compiler is able to vectorize because as we explained above the Unity Job System has rules that each variable in a job struct cannot alias any other member in the struct.

我们可以看到代码是矢量化的–编译器在这里做得很好! 编译器能够进行矢量化处理,因为如上所述,Unity Job System具有规则,即作业结构中的每个变量 都不能别名 该结构中的任何其他成员。

But there are cases that can be seen in the wild where developers are building up data structures where Burst has no information on how the aliasing works with those structures, for example:

但是,在某些情况下,开发人员正在建立数据结构的情况很普遍,而Burst没有有关别名如何与这些结构一起工作的信息,例如:

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[BurstCompile]
private struct NotEnoughAliasingInformationJob : IJobParallelFor
{
    public struct Data
    {
        public NativeArray<float> a;
        public NativeArray<float> b;
        public NativeArray<float> c;
        public NativeArray<float> o;
    }
    public Data d;
    public void Execute(int i)
    {
        d.o[i] = d.a[i] * d.b[i] + d.c[i];
    }
}

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[ BurstCompile ]
private struct NotEnoughAliasingInformationJob : IJobParallelFor
{
     public struct Data
     {
         public NativeArray < float > a ;
         public NativeArray < float > b ;
         public NativeArray < float > c ;
         public NativeArray < float > o ;
     }
     public Data d ;
     public void Execute ( int i )
     {
         d . o [ i ] = d . a [ i ] * d . b [ i ] + d . c [ i ] ;
     }
}

In the above example we’ve just wrapped the data members from the BasicJob in a new struct Data, and stored this struct as the only variable in the parent job struct. Let’s see what the Burst Inspector shows us now:

在上面的示例中,我们只是将BasicJob中的数据成员包装在一个新的结构数据中,并将该结构作为唯一变量存储在父作业结构中。 让我们看看“爆发检查器”现在向我们显示的内容:

Burst has been smart enough to vectorize this example – but at the cost of having to check that all of the pointers being used are not overlapping at the start of the loop.

Burst足够聪明,可以对这个示例进行矢量化处理-但要付出代价,即必须检查所使用的所有指针在循环开始时是否重叠。

This is because the Job System aliasing rules only give Burst guarantees about direct variable members of a struct – not anything derived from them. So Burst has to assume that the native array backing the variables a, b, c, and o is the same variable – meaning the complicated and performance draining dance of ‘Do any of these pointers actually equal each other?’. So how can we fix this? By using our [NoAlias] attribute to explain this to Burst!

这是因为作业系统别名规则仅为结构的直接变量成员提供Burst保证,而不能从其派生任何东西。 因此,Burst必须假定支持变量a,b,c和o的本机数组是同一变量-意味着 “这些指针中的任何一个实际上彼此相等吗?” 的复杂性和性能消耗之舞。 。 那么我们该如何解决呢? 通过使用我们的[NoAlias]属性向Burst解释这一点!

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[BurstCompile]
private struct WithAliasingInformationJob : IJobParallelFor
{
    public struct Data
    {
        [NoAlias]
        public NativeArray<float> a;
        [NoAlias]
        public NativeArray<float> b;
        [NoAlias]
        public NativeArray<float> c;
        [NoAlias]
        public NativeArray<float> o;
    }
    public Data d;
    public void Execute(int i)
    {
        d.o[i] = d.a[i] * d.b[i] + d.c[i];
    }
}

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[ BurstCompile ]
private struct WithAliasingInformationJob : IJobParallelFor
{
     public struct Data
     {
         [ NoAlias ]
         public NativeArray < float > a ;
         [ NoAlias ]
         public NativeArray < float > b ;
         [ NoAlias ]
         public NativeArray < float > c ;
         [ NoAlias ]
         public NativeArray < float > o ;
     }
     public Data d ;
     public void Execute ( int i )
     {
         d . o [ i ] = d . a [ i ] * d . b [ i ] + d . c [ i ] ;
     }
}

In the WithAliasingInformationJob job above, we can see that there are new [NoAlias] attributes set on the fields of Data. These [NoAlias] attributes are telling Burst that:

在上面的WithAliasingInformationJob作业中,我们可以看到在Data字段上设置了新的[NoAlias]属性。 这些[NoAlias]属性告诉Burst:

  • a, b, c, and o do not alias with any other member of Data that has a [NoAlias] attribute.

    a,b,c和o不会与具有[NoAlias]属性的其他任何Data成员混叠。

  • So each variable does not alias with any other variable in the struct because they all have the [NoAlias] attribute.

    因此,每个变量都不会与结构中的其他任何变量混叠,因为它们 具有[NoAlias]属性。

And again we’ll look at the Burst Inspector:

再次,我们将看一下Burst Inspector:

With this change we have removed all those expensive runtime pointer checks, and can just get on with running the vectorized loop – nice!

通过此更改,我们删除了所有那些昂贵的运行时指针检查,并且可以继续运行矢量化循环了-太好了!

Using the new Unity.Burst.CompilerServices.Aliasing intrinsics will ensure that you never accidentally change the code to affect aliasing again in the future. For example:

使用新的Unity.Burst.CompilerServices.Aliasing内部函数将确保您永远不会意外更改代码,以免将来再次影响别名。 例如:

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[BurstCompile]
private struct WithAliasingInformationAndIntrinsicsJob : IJobParallelFor
{
    public struct Data
    {
        [NoAlias]
        public NativeArray<float> a;
        [NoAlias]
        public NativeArray<float> b;
        [NoAlias]
        public NativeArray<float> c;
        [NoAlias]
        public NativeArray<float> o;
    }
    public Data d;
    public unsafe void Execute(int i)
    {
     // Check a does not alias with the other three.
        ExpectNotAliased(d.a.GetUnsafePtr(), d.b.GetUnsafePtr());
        ExpectNotAliased(d.a.GetUnsafePtr(), d.c.GetUnsafePtr());
        ExpectNotAliased(d.a.GetUnsafePtr(), d.o.GetUnsafePtr());
        // Check b does not alias with the other two (it has already been checked against a above).
        ExpectNotAliased(d.b.GetUnsafePtr(), d.c.GetUnsafePtr());
        ExpectNotAliased(d.b.GetUnsafePtr(), d.o.GetUnsafePtr());
        // Check that c and o do not alias (the other combinations have been checked above).
        ExpectNotAliased(d.c.GetUnsafePtr(), d.o.GetUnsafePtr());
        d.o[i] = d.a[i] * d.b[i] + d.c[i];
    }
}

1

2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[ BurstCompile ]
private struct WithAliasingInformationAndIntrinsicsJob : IJobParallelFor
{
     public struct Data
     {
         [ NoAlias ]
         public NativeArray < float > a ;
         [ NoAlias ]
         public NativeArray < float > b ;
         [ NoAlias ]
         public NativeArray < float > c ;
         [ NoAlias ]
         public NativeArray < float > o ;
     }
     public Data d ;
     public unsafe void Execute ( int i )
     {
     // Check a does not alias with the other three.
         ExpectNotAliased ( d . a . GetUnsafePtr ( ) , d . b . GetUnsafePtr ( ) ) ;
         ExpectNotAliased ( d . a . GetUnsafePtr ( ) , d . c . GetUnsafePtr ( ) ) ;
         ExpectNotAliased ( d . a . GetUnsafePtr ( ) , d . o . GetUnsafePtr ( ) ) ;
         // Check b does not alias with the other two (it has already been checked against a above).
         ExpectNotAliased ( d . b . GetUnsafePtr ( ) , d . c . GetUnsafePtr ( ) ) ;
         ExpectNotAliased ( d . b . GetUnsafePtr ( ) , d . o . GetUnsafePtr ( ) ) ;
         // Check that c and o do not alias (the other combinations have been checked above).
         ExpectNotAliased ( d . c . GetUnsafePtr ( ) , d . o . GetUnsafePtr ( ) ) ;
         d . o [ i ] = d . a [ i ] * d . b [ i ] + d . c [ i ] ;
     }
}

These checks do not cause a compiler error in the above job – which means as we already seen, Burst has enough information because of the added [NoAlias] attributes to detect and optimize this case.

这些检查 不会 在上面的工作中 导致编译器错误 -这意味着,正如我们已经看到的,由于添加了[NoAlias]属性,Burst具有足够的信息来检测和优化这种情况。

Now while this is a contrived example for the sake of conciseness in this blog, these kind of aliasing hints can provide very real-world performance benefit in your code. As we always recommend, using the Burst Inspector when iterating on code modifications you have made will ensure that you keep stepping towards a more optimized future.

现在,尽管为了简洁起见,这是一个人为的示例,但是这些别名提示可以在您的代码中提供非常真实的性能优势。 正如我们始终建议的那样,在对所做的代码修改进行迭代时使用Burst Inspector将确保您不断迈向更优化的未来。

结论 (Conclusion)

With the release of Burst 1.3.0 we provided you another set of tools to get the maximum performance from your code. With the extended and enhanced [NoAlias] support you can perfectly control how your data structures work. And the new compiler intrinsics give you a meaningful insight into how the compiler understands your code.

随着Burst 1.3.0的发布,我们为您提供了另一套工具,以使您的代码获得最佳性能。 通过扩展和增强的[NoAlias]支持,您可以完美地控制数据结构的工作方式。 而且,新的编译器内部函数使您可以洞悉编译器如何理解您的代码。

If you haven’t started with Burst yet and would like to learn more about our work on the new Data-Oriented Technology Stack (DOTS), head over to our DOTS pages, where we will be adding more learning resources and links to talks from our teams as more becomes available. 

如果您还没有开始使用Burst,并且想了解有关我们在新的面向数据技术堆栈(DOTS)上的工作的更多信息,请访问我们的 DOTS页面 ,我们将在其中添加更多的学习资源和链接,以进行演讲。我们的团队越来越多。

We always welcome your feedback – join the forum here to let us know how we can help you level up your Burst code in future.

我们始终欢迎您的反馈–在 这里 加入论坛 ,让我们知道我们将来如何帮助您升级Burst代码。

翻译自: https://blogs.unity3d.com/2020/09/07/enhanced-aliasing-with-burst/

postgresql 别名

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值