在 64 位 x86 寄存器的情况下,如果一个值的大小足够小以至于多个指令可以放入一个寄存器,是否可以在同一个寄存器中一次保存多个值?例如将两个 32 位整数装入一个寄存器。如果可能,这会是一件坏事吗?我一直在阅读寄存器,我对这个概念很陌生。
2 回答
寄存器不包含指令,但我假设您的意思是将多个值放入一个寄存器中,以便您可以使用一条指令将它们都添加。
是的,这称为SIMD。(单指令,多数据) 在 x86-64 上,SSE2(流 SIMD 扩展)保证可用,因此您有 16 个不同的 16 字节寄存器 (xmm0..15)。并且您有指令可以执行 4x 32 位浮点数、2x 64 位双精度的打包 FP add/sub/mul/div/sqrt/cmp、字节、字、双字的打包整数 add/sub/cmp/shift/etc , 和 qword 操作数大小。
(有一些空白;SSE2 不是很正交,例如最窄的移位是 16 位,压缩的最小/最大值仅适用于某些尺寸。其中一些空白由 SSE4.1 填补)。
以及元素宽度无关的按位布尔值(直到带有掩码寄存器的 AVX512...)
请参阅https://www.felixcloutier.com/x86/。 p...
像这样的指令paddw
是压缩整数。 ...ps
并且pd
是浮点打包单或打包双。
编译器经常使用 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;
}
在 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
矢量化有点像并行化,对于这样的减少(将数组求和为标量)需要关联操作。例如,FP 版本只能-ffast-math
使用 OpenMP 或使用 OpenMP 进行矢量化。
在像 RAX 这样的通用寄存器中,没有指令在没有字节边界之间进位的情况下进行 SIMD 加法(就像paddb xmm0, xmm1
会一样),它被称为 SWAR(寄存器内的 SIMD)。
在过去,这种技术在没有适当 SIMD 指令集(如 Alpha 或 MIPS64)的 ISA 上更有用。但这仍然是可能的,并且 SWAR 技术可以用作没有popcnt
指令的 popcount 之类的一部分,例如屏蔽所有其他位并移位,因此您可以有效地执行 32 个单独的加法(不能相互溢出)到2 位累加器。
如何计算 32 位整数中设置的位数?这样做,扩大到 4 位计数器,然后是 8 位,然后使用乘法来移位和相加 4 个不同的移位,并在高字节中产生总和。
寄存器不倾向于保存指令,而是保存要由指令处理的数据。
但是,如果您想将指令存储为数据,我相信(从这里)最长的 x86 指令大约是 15 个字节,或 120 位。所以,不,它不适合单个 64 位寄存器。
就在单个寄存器中保存多个数据值而言,这当然是可能的。这甚至得到了硬件的支持,甚至最早的 x86 芯片也具有ah
并al
共同构成了ax
寄存器。
and
即使没有这个,您当然可以通过使用按位运算(如、和)和位移操作(如、or
、和)将“子寄存器”插入/从寄存器中提取/提取。not
xor
shl
shr
rol
ror