我有几个与将XMM值移动到通用寄存器有关的问题。在SO上找到的所有问题都集中在相反的方面,即将gp寄存器中的值传递给XMM。
如何将XMM寄存器值(128位)移动到两个64位通用寄存器?
movq RAX XMM1 ; 0th bit to 63th bit
mov? RCX XMM1 ; 64th bit to 127th bit
同样,如何将XMM寄存器值(128位)移动到4个32位通用寄存器?
movd EAX XMM1 ; 0th bit to 31th bit
mov? ECX XMM1 ; 32th bit to 63th bit
mov? EDX XMM1 ; 64th bit to 95th bit
mov? ESI XMM1 ; 96th bit to 127 bit
答案 0 :(得分:11)
您不能直接将XMM寄存器的高位移动到通用寄存器中 您必须遵循两个步骤,这可能涉及也可能不涉及到记忆的往返或破坏登记。
寄存器
movq rax,xmm0 ;lower 64 bits
movhlps xmm0,xmm0 ;move high 64 bits to low 64 bits.
movq rbx,xmm0 ;high 64 bits.
通过内存
movdqu [mem],xmm0
mov rax,[mem]
mov rbx,[mem+8]
慢,但不会破坏xmm寄存器
mov rax,xmm0
pextrq rbx,xmm0,1 ;3 cycle latency on Ryzen!
对于32位,代码类似:
寄存器
movd eax,xmm0
psrldq xmm0,xmm0,4 ;shift 4 bytes to the right
movd ebx,xmm0
psrldq xmm0,xmm0,4
movd ecx,xmm0
psrlq xmm0,xmm0,4
movd edx,xmm0
通过内存
movdqu [mem],xmm0
mov eax,[mem]
mov ebx,[mem+4]
mov ecx,[mem+8]
mov edx,[mem+12]
慢,但不会破坏xmm寄存器
mov eax,xmm0
pextrd ebx,xmm0,1 ;3 cycle latency on Skylake!
pextrd ecx,xmm0,2
pextrd edx,xmm0,3
64位移位变体可以在2个周期内运行。 pextrq
版本最少需要4个版本。对于32位,数字分别为4和10。
答案 1 :(得分:1)
在Intel SnB系列(包括Skylake)上,随机播放+ movq
或movd
与pextrq
/ d
具有相同的效果。它解码为一个shuffle uop和一个movd
uop,所以这并不奇怪。
在AMD Ryzen上,pextrq
显然比shuffle + movq
的延迟低1个周期。根据{{3}},pextrd/q
是3c延迟,movd/q
也是{1}}。这是一个巧妙的技巧(如果它是准确的),因为pextrd/q
确实解码为2 uops(而movq
为1)。
由于shuffle具有非零延迟,因此Shuffle + movq
总是严格地比Ryzen上的pextrq
严重(除了可能的前端解码/ uop-cache效果)。
用于提取所有元素的纯ALU策略的主要缺点是吞吐量:它需要大量ALU uop,并且大多数CPU只有一个执行单元/端口可以将数据从XMM移动到整数。存储/重新加载对于第一个元素具有更高的延迟,但是更好的吞吐量(因为现代CPU可以在每个周期执行2次加载)。如果周围的代码受到ALU吞吐量的瓶颈,则存储/重新加载策略可能会很好。也许使用movd
或movq
来执行低元素,这样无序执行可以在任何使用它时启动,而其余的矢量数据通过存储转发。
另一个值得考虑的选择(除了Johan提到的)将32位元素提取到整数寄存器是用整数移位做一些“改组”:
mov rax,xmm0
# use eax now, before destroying it
shr rax,32
pextrq rcx,xmm0,1
# use ecx now, before destroying it
shr rcx, 32
shr
可以在Intel Haswell / Skylake的p0或p6上运行。 p6没有矢量ALU,所以如果你想要低延迟但是对矢量ALU压力很低,这个序列非常好。
或者如果你想保留它们:
mov rax,xmm0
rorx rbx, rax, 32 # BMI2
# shld rbx, rax, 32 # alternative that has a false dep on rbx
# eax=xmm0[0], ebx=xmm0[1]
pextrq rdx,xmm0,1
mov ecx, edx # the "normal" way, if you don't want rorx or shld
shr rdx, 32
# ecx=xmm0[2], edx=xmm0[3]
答案 2 :(得分:-1)
以下处理get和set并且似乎有效(我认为它的AT& T语法):
#include <iostream>
int main() {
uint64_t lo1(111111111111L);
uint64_t hi1(222222222222L);
uint64_t lo2, hi2;
asm volatile (
"movq %3, %%xmm0 ; " // set high 64 bits
"pslldq $8, %%xmm0 ; " // shift left 64 bits
"movsd %2, %%xmm0 ; " // set low 64 bits
// operate on 128 bit register
"movq %%xmm0, %0 ; " // get low 64 bits
"movhlps %%xmm0, %%xmm0 ; " // move high to low
"movq %%xmm0, %1 ; " // get high 64 bits
: "=x"(lo2), "=x"(hi2)
: "x"(lo1), "x"(hi1)
: "%xmm0"
);
std::cout << "lo1: [" << lo1 << "]" << std::endl;
std::cout << "hi1: [" << hi1 << "]" << std::endl;
std::cout << "lo2: [" << lo2 << "]" << std::endl;
std::cout << "hi2: [" << hi2 << "]" << std::endl;
return 0;
}