对于64位x86寄存器,如果一个值的大小足够小,使得一个寄存器可以容纳多个指令,那么是否可以一次在一个寄存器中保存多个值?例如,将两个32位int装入一个寄存器。如果可能的话,这将是一件坏事吗?我一直在阅读寄存器,并且对这个概念还很陌生。
答案 0 :(得分:3)
寄存器不保存指令,但我假设您的意思是将多个值装入一个寄存器,以便您可以用一条指令将它们都添加。
是的,这称为SIMD. (Single Instruction, Multiple Data) 。在x86-64上,保证SSE2(流式SIMD扩展名)可用,因此您有16个不同的16字节寄存器(xmm0。 15)。而且您有说明可以对字节,字,dword进行打包的FP add / sub / mul / div / sqrt / cmp 4x 32位浮点数,2x 64位double,打包整数add / sub / cmp / shift / etc和qword操作数大小。
(存在一些间隙; SSE2不是非常正交的,例如,最窄的移位是16位,压缩的最小/最大仅适用于某些大小。其中一些间隙由SSE4.1填充)。
与元素宽度无关的按位布尔运算(直到带掩码寄存器的AVX512 ...)
请参见https://www.felixcloutier.com/x86/。 p...
之类的paddw
条指令是打包整数。 ...ps
和pd
是浮点压缩单或压缩双。
编译器经常使用movdqa
之类的SSE / SSE2指令将内存清零或以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;
}
使用xcc-64 on the Godbolt compiler explorer的GCC9.3 -O3进行这样的编译
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进行矢量化。
在像RAX这样的通用寄存器中,没有指令进行SIMD加法而不在字节边界之间进位(就像paddb xmm0, xmm1
这样),它被称为SWAR(寄存器中的SIMD)。
过去,这种技术在没有适当的SIMD指令集(如Alpha或MIPS64)的ISA上更有用。但这仍然是可能的,SWAR技术可以在没有popcnt
指令(例如,掩盖每隔一位并进行移位,这样您就可以有效地将32个独立的加法(不会互相溢出)加到2位累加器中。
How to count the number of set bits in a 32-bit integer?中显示的popcnt bithack做到了这一点,将其扩宽为4位计数器,然后扩展为8位,然后使用乘法进行4次不同的移位加法运算,并在高字节中产生总和。
答案 1 :(得分:2)
寄存器不倾向于保存指令,而是保存要由指令处理的数据。
但是,如果您想要将指令存储为数据,我相信(来自here),最长的x86指令大约为15个字节或120位。因此,不,它不能放入单个64位寄存器中。
就在单个寄存器中保存多个数据值而言,这肯定是可能的。甚至硬件也支持此功能,即使是最早的x86芯片也具有ah
和al
,它们共同构成了ax
寄存器。
即使没有这些,您当然也可以通过使用按位运算(例如and
,or
,not
和{{ 1}})和位移操作(如xor
,shl
,shr
和rol
)。