Как переместить 128-битные значения непосредственно в регистры XMM

Уже есть вопрос по этому поводу, но он был закрыт как "неоднозначный", поэтому я Открываю новый - нашел ответ, может и другим поможет.

Возникает вопрос: как написать последовательность ассемблерного кода для инициализации регистра XMM 128-битным непосредственным (постоянным) значением?


person Virgil    schedule 11.07.2011    source источник


Ответы (5)


Просто хотел добавить, что можно прочитать о создании различных констант с помощью сборки в руководстве Agner Fog Оптимизация подпрограмм в сборке language, Создание констант, раздел 13.8, с. 124.

person Norbert P.    schedule 11.07.2011
comment
Спасибо, забыл про ту :). Кстати, в книге предлагается SHUFPD, который работает, но в этом случае я думаю, что мое предложение с MOVLHPS лучше (короче, по крайней мере) - person Virgil; 11.07.2011

Вы можете сделать это так, используя всего одну movaps инструкцию:

.section .rodata    # put your constants in the read-only data section
.p2align 4          # align to 16 = 1<<4
LC0:
        .long   1082130432
        .long   1077936128
        .long   1073741824
        .long   1065353216

.text
foo:
        movaps  LC0(%rip), %xmm0

Загрузка его с загрузкой данных обычно предпочтительнее встраивания в поток инструкций, особенно из-за того, сколько инструкций требуется. Это несколько лишних мопов для выполнения ЦП для произвольной константы, которая не может быть сгенерирована из всех единиц с парой сдвигов.

Если это проще, вы можете поместить константы прямо перед или после функции, которую вы jit-компилируете, а не в отдельном разделе. Но поскольку процессоры разделили кеши L1d / L1i и TLB, обычно лучше группировать константы отдельно от инструкций.

Если обе половины вашей константы одинаковы, вы можете загрузить ее с помощью SSE3
movddup (m64), %xmm0.

person Paul R    schedule 11.07.2011
comment
Верно, но я генерировал код динамически, было проще добавить код, чем добавить раздел памяти :) (и, кстати, ваш пример должен использовать .align 16, верно?) - person Virgil; 11.07.2011
comment
@Virgil: разные версии инструментальной цепочки gcc немного несовместимы в этом, но обычно директива .align принимает аргумент степени 2, поэтому .align 4 означает выравнивание по кратному 2 ^ 4 = 16 байтам. - person Paul R; 11.07.2011
comment
Как бы вы это сделали на x86-32? Я не могу понять, как перевести относительную адресацию компьютера. - person Janus Troelsen; 10.11.2011
comment
Я использую align 16, и он работает, как ожидалось (т.е. Верджил прав.) Я не знаю, изменилось ли это, но .align 4 может привести к сбою с исключением выравнивания. - person Alexis Wilke; 20.01.2013
comment
@JanusTroelsen вы пробовали (% eip) - с 'e' вместо 'r'. - person Alexis Wilke; 20.01.2013
comment
@Alexis: проверьте адрес LC0, вы можете обнаружить, что он выровнен по 2 ^ 16, то есть адрес xxxxxxxxxxxx0000, а не просто xxxxxxxxxxxxxxx0. Это не большая проблема, но если вы будете делать это часто, ваша программа может стать очень фрагментированной. - person Paul R; 20.01.2013
comment
@PaulR да, и глядя на мой код, я увидел адрес, заканчивающийся на xxxB0, так что на самом деле это было всего 4 бита (выровнено 16 байт). - person Alexis Wilke; 22.01.2013
comment
@Alexis: Хорошо, хорошо - это просто то, о чем нужно знать, если вы переключаетесь между разными версиями инструментальной цепочки gcc. - person Paul R; 22.01.2013
comment
.p2align 4 было бы хорошим выбором. Это всегда означает выравнивание по степени двойки и было введено, чтобы остановить безумие .align, означающего разные вещи на разных ассемблерах (или версиях одного и того же ассемблера?). Я думаю, что он существует дольше, чем SSE, поэтому рекомендуется его рекомендовать. - person Peter Cordes; 13.04.2017

В качестве одного из 10000 способов сделать это используйте SSE4.1 pinsrq

mov    rax, first half
movq   xmm0, rax      ; better than pinsrq xmm0,rax,0 for performance and code-size

mov    rax, second half
pinsrq xmm0, rax, 1
person Pierre    schedule 14.06.2012
comment
Где pinsertq задокументировано? Я не мог найти эту инструкцию ни в одном из руководств Intel. - person Sergey L.; 14.10.2013
comment
: Ошибка: несоответствие типа операнда для `pinsrq ' - person thang; 08.02.2016
comment
Инструкция movq не позволяет использовать общий регистр в качестве второго операнда. Так что это «быстрее» только потому, что не удается собрать очень быстро. С другой стороны, трюк с pinsrq работает. - person David Wohlferd; 25.03.2017
comment
@DavidWohlferd: есть две формы movq: вы, вероятно, думаете о MOVQ xmm1, xmm2/m64, которые могут собирать в 32- или 64-битном режиме. Но, конечно, здесь используется форма MOVQ xmm, r/m64, которая является REX + MOVD и предназначена только для доступен в 64-битном режиме. Очевидно, некоторые ассемблеры до сих пор называют это movd, поэтому, если он не ассемблируется, попробуйте movd xmm0, rax. Или лучше загрузите константу с movdqa. - person Peter Cordes; 13.04.2017

Лучшее решение (особенно если вы хотите придерживаться SSE2 - т.е. избегать использования AVX) для инициализации двух регистров (скажем, xmm0 и xmm1) двумя 64-битными половинами вашего непосредственного значения, выполните MOVLHPS xmm0, xmm1. инициализировать 64-битное значение, самое простое решение - использовать регистр общего назначения (скажем, AX), а затем использовать MOVQ для передачи его значения в регистр XMM. Итак, последовательность будет примерно такой:

MOV RAX, <first_half>
MOVQ XMM0, RAX
MOV RAX, <second_half>
MOVQ XMM1, RAX
MOVLHPS XMM0,XMM1
person Virgil    schedule 11.07.2011
comment
Часть о SSE2 и AVX скорее non sequitur - возможно, вы имеете в виду SSE3 / SSSE3 / SSE4, а не AVX? - person Paul R; 11.07.2011
comment
Я имел в виду флаг функции CPID. SSE3 / 4 вам особо не поможет. Я думаю, что нашел более простой способ сделать это с помощью инструкций AVX, но проигнорировал его, поскольку поддерживающие его процессоры не получили широкого распространения. - person Virgil; 11.07.2011
comment
@Virgil: Пол прав: PINSRQ xmm0, rax, 1 из SSE4.1 может заменить movq / movlhps. Кроме того, вы должны говорить RAX, а не только AX. AX означает, в частности, младшие 16 бит RAX. Вы могли назвать это А, но это сбивает с толку. В любом случае, это хуже, чем просто загрузка с помощью инструкции загрузки. - person Peter Cordes; 29.01.2016
comment
Кроме того, для значения, которое будет использоваться с целочисленными инструкциями, punpcklqdq xmm0, xmm1 может быть лучшим выбором, чем movlhps. Для констант явно неупорядоченное выполнение может скрыть задержку обхода от перетасовки FP до целочисленной инструкции (на процессорах, где это имеет значение), но это не повредит. В любом случае, я думаю, что в большинстве случаев лучше просто загрузить константу из раздела .rodata, чем вставлять ее в поток инструкций. Обычно имеет значение пространство uop-cache, равно как и пропускная способность внешнего интерфейса. Одиночный movdqa намного быстрее, если он не пропадает в кеше. Но этого не произойдет, если это будет выполняться часто - person Peter Cordes; 13.04.2017

Существует несколько способов встраивания констант в поток инструкций:

  1. используя непосредственные операнды
  2. путем загрузки с адресов, относящихся к ПК

Таким образом, хотя нет возможности выполнить немедленную загрузку в регистр XMM, можно выполнить загрузку относительно ПК (в 64-битном режиме) из значения, хранящегося «рядом» с местом выполнения кода. Это создает что-то вроде:

.align 4
.val:
    .long   0x12345678
    .long   0x9abcdef0
    .long   0xfedbca98
    .long   0x76543210
func:
     movdqa .val(%rip), %xmm0

При разборке:

0000000000000000 :
   0:   78 56 34 12 f0 de bc 9a
   8:   98 ca db fe 10 32 54 76

0000000000000010 :
  10:   66 0f 6f 05 e8 ff ff    movdqa -0x18(%rip),%xmm0        # 0 

который имеет чрезвычайно компактный размер 23 байта.

Другие варианты - создать значение в стеке и снова загрузить его оттуда. В 32-битной системе x86, где у вас нет %rip-относительного доступа к памяти, можно сделать это за 24 байта (при условии, что указатель стека выровнен при входе; в противном случае требуется невыровненная загрузка):

00000000 :
   0:   68 78 56 34 12          push   $0x12345678
   5:   68 f0 de bc 9a          push   $0x9abcdef0
   a:   68 98 ca db fe          push   $0xfedbca98
   f:   68 10 32 54 76          push   $0x76543210
  14:   66 0f 6f 04 24          movdqa (%esp),%xmm0

В 64-битном режиме (выравнивание указателя стека при входе в функцию там гарантируется ABI) это займет 27 байт:

0000000000000000 :
   0:   48 b8 f0 de bc 9a 78 56 34 12   movabs $0x123456789abcdef0,%rax
   a:   50                              push   %rax
   b:   48 b8 10 32 54 76 98 ba dc fe   movabs $0xfedcba9876543210,%rax
  15:   50                              push   %rax
  16:   66 0f 6f 04 24                  movdqa (%rsp),%xmm0

Если вы сравните любую из них с версией MOVLHPS, вы заметите, что она самая длинная:

0000000000000000 :
   0:   48 b8 f0 de bc 9a 78 56 34 12   movabs $0x123456789abcdef0,%rax
   a:   66 48 0f 6e c0                  movq   %rax,%xmm0
   f:   48 b8 10 32 54 76 98 ba dc fe   movabs $0xfedcba9876543210,%rax
  19:   66 48 0f 6e c8                  movq   %rax,%xmm1
  1e:   0f 16 c1                        movlhps %xmm1,%xmm0

на 33 байтах.

Другое преимущество загрузки непосредственно из памяти команд состоит в том, что movdqa не зависит от чего-либо предыдущего. Скорее всего, первая версия, предоставленная @Paul R, является самой быстрой из возможных.

person FrankH.    schedule 12.07.2011
comment
Хорошая работа по представлению каждой возможности и показу, какая из них самая короткая. Лично я предпочитаю родственника IP, он четкий и очень короткий. С другой стороны, это одно, возможно, дорогостоящее попадание в память (в отличие от кода, который всегда должен быть в кеше). - person Alexis Wilke; 20.01.2013
comment
Wrt. для кэширования, загружая константу с адреса в той же строке кэша, что и загружающий ее код, у вас есть хорошие шансы, что она будет перегружена кешем - поскольку исполняемый код должен быть извлечен к моменту его запуска, и, по крайней мере, L2 унифицирован, скорее всего получится не хуже, чем накладные расходы на попадание в кэш L2 для загрузки константы. - person FrankH.; 21.01.2013
comment
@AlexisWilke: Кэш uop крошечный по сравнению с ним, и он стоит дорого. Обычно не стоит встраивать константы 128b в поток insn. Возможно, стоит сгенерировать простые на лету (например, pcmpeqw xmm0,xmm0 / psrld xmm0, 31 для генерации вектора из четырех 32-битных целочисленных 1 значений) или, может быть, сразу переместить в регистр movq и транслировать его с pshufd. - person Peter Cordes; 29.01.2016