128-битные значения-от регистров XMM до общего назначения
у меня есть несколько вопросов, связанных с перемещением значений 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-бит) в четыре 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
3 ответов
вы не можете переместить верхние биты регистра 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 register
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 register
mov eax,xmm0
pextrd ebx,xmm0,1 ;3 cycle latency on Skylake!
pextrd ecx,xmm0,2
pextrd edx,xmm0,3
64-разрядный вариант сдвига может работать в 2 циклах. The pextrq
версия принимает минимум 4. Для 32-разрядных чисел это 4 и 10 соответственно.
на Intel SnB-family (включая Skylake), shuffle+movq
или movd
имеет ту же производительность, что и pextrq
/d
. Он декодирует до перетасовки uop и A movd
uop, так что это не удивительно.
на AMD Ryzen,pextrq
по-видимому, имеет 1 цикл более низкую задержку, чем shuffle + movq
. pextrd/q
является задержкой 3c, а также movd/q
, по данным таблицы Агнера Фога. Это аккуратный трюк (если он точен), так как pextrd/q
декодирует до 2 uops (против 1 для movq
).
поскольку перетасовки имеют ненулевую задержку, перетасовка+movq
всегда строго хуже, чем pextrq
на Ryzen (за исключением возможных интерфейсных эффектов декодирования / uop-cache).
основным недостатком чистой стратегии ALU для извлечения всех элементов является пропускная способность: требуется много uops ALU, и большинство процессоров имеют только один блок / порт выполнения, который может перемещать данные из XMM в integer. Store/reload имеет более высокую задержку для первого элемента, но лучшую пропускную способность (потому что современные процессоры могут выполнять 2 нагрузки за цикл). Если окружающий код ограничен пропускной способностью ALU, стратегия хранения/перезагрузки может быть хорошей. Возможно, сделайте низкий элемент с movd
или movq
таким образом, выполнение вне заказа может начать работу с тем, что использует его, в то время как остальные векторные данные проходят через переадресацию магазина.
еще один вариант стоит рассмотреть (помимо того, что упомянул Йохан) для извлечения 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
может работать на p0 или p6 в Intel Haswell/Skylake. p6 не имеет векторных ALUs, поэтому эта последовательность довольно хороша, если вы хотите низкую задержку, но и низкое давление на векторные ALUs.
или если вы хотите, чтобы держать их вокруг:
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]
следующие дескрипторы 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 , %%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;
}