Memory Aliasing
wiki给出的定义如下:
In computing, aliasing describes a situation in which a data location in memory can be accessed through different symbolic names in the program. Thus, modifying the data through one name implicitly modifies the values associated with all aliased names, which may not be expected by the programmer. As a result, aliasing makes it particularly difficult to understand, analyze and optimize programs. Aliasing analysers intend to make and compute useful information for understanding aliasing in programs.
简单来说,aliasing就是同一块内存被不同的符号(变量)所表示,从而造成了程序运行时问题,以及编译器优化时的问题。
Unity的描述
首先Unity给了一个范例:
[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];
}
}
}
No memory aliasing
对于No memory aliasing的情况而言,输入和输出的一一对应,也就是说代码按照字面的意思执行。
No memory aliasing with the auto-vectorizer
对于No memory aliasing with the auto-vectorizer的情况而言,编译器做了优化,通过SIMD技术这段代码的执行效率得到了提高,但代码的执行顺序已经不是按照代码的字面意思。
Memory aliasing
当发生下列情况,比如output[0]实际的地址是input[1],这时就会发生memory aliasing,可以看到这个时候由于memory aliasing,导致后续的结果和Input[0]是完全一样的。
Memory aliasing with invalid vectorized code
当memory aliasing和auto-vectorizer同时发生时,由于一次vector最多只能操作4个31bit的int数字,因此可以看到output[0-3]是预想的结果,但在进行到第二次vector时,由于output[3]实际指向的是input[4],因此字母e就完全被d覆盖。最终导致出现了两次d,而e的数据丢失。
NoAlias Attribute
Unity Burst提供了[NoAlias]属性,来告诉编译器关于指针和结构体的aliasing信息,该属性可以被用下一下几种情况:
- On a function parameter it signifies that the parameter does not alias with any other parameter to the function.
- On a struct 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 returned from the same function.
Unity官方对几种情况针对性地举了例子。
NoAlias Function Parameter
int Foo(ref int a, ref int b)
{
b = 13;
a = 42;
return b;
}
对应的汇编如下:
mov dword ptr [rdx], 13
mov dword ptr [rcx], 42
mov eax, dword ptr [rdx]
ret
当给函数参数加上[NoAlias]之后:
int Foo([NoAlias] ref int a, ref int b)
{
b = 13;
a = 42;
return b;
}
对应的汇编如下:
mov dword ptr [rdx], 13
mov dword ptr [rcx], 42
mov eax, 13
ret
可以看到,加上[NoAlias]之后,向eax寄存器的的mov操作的dest,从一块内存转变为了一个常数,熟悉汇编的都知道这样会提升mov的执行效率。
NoAlias Struct Field
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];
}
对应的汇编如下:
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
当给a,b加上[NoAlias]之后:
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];
}
生成的汇编如下:
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
加上[NoAlias]可以看到eax寄存器的dest从内存变为了常数42,编译器对此进行了优化。
总结
上面的两个案例总的来说就是一句话,当编译器不知道它要操作的这块memory是否aliasing时,它会采取比较保守的策略,但当编译器明确知道这块memory不是aliasing时,它便会对该代码块采用相应的优化策略。因此,在一般情况下,当no memory aliasing时,尽可能明确告诉编译器这件事情,编译器才能采用更好的优化策略。