如何解决 SIMD 内联 asm 的左值问题,在 2D 数组中使用内存输出操作数?

How is the lvalue problem solved for SIMD inline asm with memory output operands in a 2D array?

我正在尝试编写一个函数,使用 ymm 寄存器用零填充我的浮点矩阵。

没多久我写了这个函数:

void fillMatrixByZeros(float matrix[N][N]){
    for (int k = 0; k < N; k += 8){
        for (int i = 0; i < N; ++i){
            asm volatile (
                "vxorps %%ymm0, %%ymm0, %%ymm0;"
                "vmovups %%ymm0, (%0)"
                : "=m"(matrix[i] + k)
                : 
                : "%ymm0", "memory"
            );
        }
    }
}

我试图编译我的整个代码,但我得到了这个错误:

prog.cpp: In function ‘void fillMatrixByZeros(float (*)[16])’:
prog.cpp:35:8: error: lvalue required in asm statement
   35 |       );
      |        ^
prog.cpp:35:8: error: invalid lvalue in asm output 0

我得出的结论是 matrix[i]+k 是一个右值或类似的东西,所以它不能在那里使用。

经过谷歌搜索,我想到了两个解决方案:

第一个:

void fillMatrixByZeros(float matrix[N][N]){
    for (int k = 0; k < N; k += 8){
        for (int i = 0; i < N; ++i){
            asm volatile (
                "vxorps %%ymm0, %%ymm0, %%ymm0;"
                "vmovups %%ymm0, (%0)"
                : 
                : "r"(matrix[i] + k)
                : "%ymm0", "memory"
            );
        }
    }
}

第二:

void fillMatrixByZeros(float matrix[N][N]){
    long long int matrixPointer;
    for (int k = 0; k < N; k += 8){
        for (int i = 0; i < N; ++i){
            asm volatile (
                "vxorps %%ymm0, %%ymm0, %%ymm0;"
                "vmovups %%ymm0, (%0)"
                : "=r"(matrixPointer)
                : "0"(matrix[i] + k)
                : "%ymm0", "memory"
            );
        }
    }
}

这些功能正常工作。我想知道为什么。

为什么第一个函数没有左值问题?而第二个函数是怎么回事?

您不能分配给 matrix[i] + k,因此它不是左值。 m 约束需要内存中的对象,而不是它的地址。因此,要解决此问题,请提供您要分配给的对象而不是其地址:

void fillMatrixByZeros(float matrix[N][N]){
    for (int k = 0; k < N; k += 8){
        for (int i = 0; i < N; ++i){
            asm volatile (
                "vxorps %%ymm0, %%ymm0, %%ymm0;"
                "vmovups %%ymm0, %0"
                : "=m"(matrix[i][k])
                : 
                : "%ymm0", "memory"
            );
        }
    }
}

这是在内联汇编语句中访问内存中对象的正确方法。

使用 r 约束和操作数地址的解决方案,然后也进行显式取消引用工作。但它们可能效率较低,因为它们阻止编译器使用其他寻址模式,如 SIB 寻址模式。相反,它必须首先具体化寄存器中的地址。

你最后一个例子有点傻。在将 matrixPointer = matrix[i] + k 传递给内联汇编语句之前,它使用耦合的 asm 操作数来执行 matrixPointer = matrix[i] + k 。这是一种非常迂回的方式,根本不需要。

就是说,为了进一步提高效率,您应该将 ymm0 的清除提升到循环之外。也许是这样的?

#include <immintrin.h>

#define N 1000

void fillMatrixByZeros(float matrix[N][N]){
    for (int k = 0; k < N; k += 8){
        for (int i = 0; i < N; ++i){
            asm volatile (
                "vmovups %1, %0"
                : "=m"(matrix[i][k])
                : "x"(_mm256_setzero_ps())
                : "memory"
            );
        }
    }
}

请注意,仅调用 memset 的性能可能比手动滚动的内联汇编好得多。