Luk*_*vis 3 assembly x86-64 simd cpu-registers swar
对于 64 位 x86 寄存器,如果一个值的大小足够小以至于多个指令可以放入一个寄存器中,是否可以在同一寄存器中一次保存多个值?例如,将两个 32 位整数装入一个寄存器。如果可能的话,这会是一件坏事吗?我一直在阅读寄存器,并且对这个概念很陌生。
寄存器不保存指令,但我假设您的意思是将多个值放入一个寄存器中,以便您可以使用一条指令将它们相加。
是的,这就是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 个不同移位的移位加法,并在高字节中产生总和。