一个寄存器可以同时保存多个值吗?

Luk*_*vis 3 assembly x86-64 simd cpu-registers swar

对于 64 位 x86 寄存器,如果一个值的大小足够小以至于多个指令可以放入一个寄存器中,是否可以在同一寄存器中一次保存多个值?例如,将两个 32 位整数装入一个寄存器。如果可能的话,这会是一件坏事吗?我一直在阅读寄存器,并且对这个概念很陌生。

Pet*_*des 5

寄存器不保存指令,但我假设您的意思是将多个放入一个寄存器中,以便您可以使用一条指令将它们相加。


是的,这就是SIMD。(单指令,多数据) 在 x86-64 上,SSE2(流 SIMD 扩展)保证可用,因此您有 16 个不同的 16 字节寄存器 (xmm0..15)。并且您有可以对 4x 32 位浮点、2x 64 位双精度、打包整数 add/sub/cmp/shift/等进行字节、字、双字的打包 FP add/sub/mul/div/sqrt/cmp 的指令和 qword 操作数大小。

(有一些间隙;SSE2 不是很正交,例如最窄移位为 16 位,压缩最小/最大仅适用于某些大小。其中一些间隙由 SSE4.1 填充)。

以及元素宽度无关的按位布尔值(直到带有掩码寄存器的 AVX512...)

请参阅https://www.felixcloutier.com/x86/。 像压缩整数 p...这样的指令。并且是浮点压缩单精度或压缩双精度。paddw...pspd

编译器经常使用 SSE/SSE2 指令,例如movdqa将内存归零或复制 16 字节块,以及对数组循环进行“向量化”(使用 SIMD 计算)。例如,GCC 7 或 8 及更高版本知道如何使用 RAX 将相邻结构成员或数组元素的加载/存储合并为标量加载或存储。

例如数组的总和:

int sumarr(const int *arr)
{
    int sum = 0;
    for(int i=0; i < 10240; i++) {
        sum += arr[i];
    }
    return sum;
}
Run Code Online (Sandbox Code Playgroud)

在 Godbolt 编译器资源管理器上使用 GCC9.3 -O3 for x86-64 进行这样的编译

sumarr:
        lea     rax, [rdi+40960]            # endp = arr + size
        pxor    xmm0, xmm0
.L2:                                        # do {
        movdqu  xmm2, XMMWORD PTR [rdi]        # v = arr[i + 0..3]
        add     rdi, 16                        # p += 4
        paddd   xmm0, xmm2                     # sum += v  // packed addition of 4 elements
        cmp     rax, rdi
        jne     .L2                         # }while(p != endp)
   ... then a horizontal vector sum ...
        MOVD eax, xmm0
        ret
Run Code Online (Sandbox Code Playgroud)

矢量化有点像并行化,对于这样的简化(将数组求和为标量)需要关联运算。例如,FP 版本只能使用-ffast-mathOpenMP 进行矢量化。


在像 RAX 这样的通用寄存器中,没有指令进行 SIMD 加法而无需在字节边界之间进行进位(就像paddb xmm0, xmm1本来的那样),它被称为 SWAR(寄存器内的 SIMD)。

过去,这种技术在没有适当 SIMD 指令集(如 Alpha 或 MIPS64)的 ISA 上更有用。但这仍然是可能的,并且 SWAR 技术可以用作诸如没有popcnt指令的 popcount 之类的东西的一部分,例如屏蔽掉所有其他位并移位,这样你就可以有效地进行 32 个单独的加法(不能互相溢出) 2 位累加器。

如何计算 32 位整数中设置的位数?中所示的 popcnt bithack 为此,先扩展到 4 位计数器,然后扩展到 8 位,然后使用乘法进行 4 个不同移位的移位加法,并在高字节中产生总和。