Memory Aliasing问题详解(持续更新...)

694 阅读2分钟

Memory Aliasing

wiki给出的定义如下:

In computingaliasing 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的情况而言,输入和输出的一一对应,也就是说代码按照字面的意思执行。

image.png

No memory aliasing with the auto-vectorizer

对于No memory aliasing with the auto-vectorizer的情况而言,编译器做了优化,通过SIMD技术这段代码的执行效率得到了提高,但代码的执行顺序已经不是按照代码的字面意思。

image.png

Memory aliasing

当发生下列情况,比如output[0]实际的地址是input[1],这时就会发生memory aliasing,可以看到这个时候由于memory aliasing,导致后续的结果和Input[0]是完全一样的。

image.png

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的数据丢失。

image.png

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时,尽可能明确告诉编译器这件事情,编译器才能采用更好的优化策略。

Reference

Aliasing

Understanding C/C++ Strict Aliasing

Memory Aliasing and noalias