Любой способ переместить 2 байта в 32-битной x86 с использованием MOV, не вызывая переключения режима или остановки процессора?

Если я хочу переместить 2 неподписанных байта из памяти в 32-разрядный регистр, могу ли я сделать это с помощью MOV инструкция и нет режима переключения?

Я заметил, что вы можете сделать это с MOVSE а также MOVZE инструкции. Например, с MOVSE кодировка 0F B7 перемещает 16 бит в 32-битный регистр. Это инструкция из 3 циклов.

В качестве альтернативы, я думаю, я мог бы переместить 4 байта в регистр, а затем каким-то образом CMP только два из них как-то.

Какая самая быстрая стратегия для получения и сравнения 16-битных данных на 32-битной x86? Обратите внимание, что я в основном делаю 32-битные операции, поэтому я не могу переключиться в 16-битный режим и оставаться там.


К сведению непосвященных: проблема в том, что 32-разрядные процессоры Intel x86 могут MOV 8-битные данные и 16-битные ИЛИ 32-битные данные в зависимости от того, в каком режиме они находятся. Этот режим называется настройкой "D-бит". Вы можете использовать специальные префиксы 0x66 и 0x67, чтобы использовать режим не по умолчанию. Например, если вы находитесь в 32-разрядном режиме и перед командой указывается 0x66, это приведет к тому, что операнд будет считаться 16-разрядным. Единственная проблема заключается в том, что это приводит к значительному снижению производительности.

2 ответа

movzx Медленно работает на древней микроархитектуре P5 (оригинальный Pentium), а не в этом столетии. Процессоры под маркой Pentium, основанные на новейших микроархитектурах, таких как Pentium G3258 (Haswell, выпуск оригинального Pentium с 20-й годовщиной), представляют собой совершенно разные звери и работают как эквивалентные i3, но без AVX, BMI2 или гиперпоточности.

Не настраивайте современный код, основанный на рекомендациях / цифрах P5. Однако Knight's Corner (Xeon Phi) основан на модифицированной микроархитектуре P54C, поэтому, возможно, он имеет медленную movzx также. Ни Agner Fog, ни Instlatx64 не имеют числа пропускной способности / задержки для каждой команды для KNC.


Использование инструкции размера 16-битного операнда не переводит весь конвейер в 16-битный режим и не вызывает большого успеха. См . Pdf-файл Agner Fog для изучения микроархитектуры процессоров x86 (включая такие же старые, как Intel P5 (оригинальный Pentium), о которых вы почему-то говорите).

Запись 16-разрядного регистра, а затем чтение полного 32- и 64-разрядного регистра выполняется медленно на некоторых процессорах (частичная остановка регистра при объединении в семействе Intel P6). В других случаях запись 16-битного регистра сливается со старым значением, поэтому при записи существует ложная зависимость от старого значения полного регистра, даже если вы никогда не читаете полный регистр. (Обратите внимание, что Haswell/Skylake переименовывает только AH отдельно, в отличие от Sandybridge, который (подобно Core2/Nehalem) также переименовывает AL / AX отдельно от RAX, но сливается без остановки.)


Если вы не особенно заботитесь о порядке P5 (или, возможно, Knight's Corner Xeon Phi, на основе того же ядра, но IDK, если movzx там тоже медленно) ИСПОЛЬЗУЙТЕ ЭТО:

movzx   eax, word [src1]        ; as efficient as a 32-bit MOV load on most CPUs
cmp      ax, word [src2]

Префикс размера операнда для cmp эффективно декодирует на всех современных процессорах. Чтение 16-битного регистра после записи полного регистра всегда хорошо, и 16-битная загрузка для другого операнда также хороша.

Префикс размера операнда не меняет длину, потому что там нет imm16 / imm32. например cmp word [src2], 0x7F хорошо (он может использовать расширенный знак imm8), но
cmp word [src2], 0x80 нужен imm16 и будет зависать LCP на некоторых процессорах Intel. (Без префикса размера операнда тот же код операции имел бы imm32, то есть остальная часть инструкции была бы другой длины). Вместо этого используйте mov eax, 0x80 / cmp word [src2], ax,

Префикс размера адреса может изменяться по длине в 32-битном режиме (disp32 или disp16), но мы не хотим использовать 16-битные режимы адресации для доступа к 16-битным данным. Мы все еще используем [ebx+1234] (или же rbx) не [bx+1234],


На современных x86: Intel P6 / семейство SnB / Atom / Silvermont, AMD начиная с версии K7, то есть все, что было сделано в этом веке, новее, чем реальный P5 Pentium, movzx нагрузки очень эффективны.

На многих процессорах порты загрузки напрямую поддерживают movzx (а иногда и movsx), поэтому он работает только как load uop, а не как load + ALU.

Данные из таблиц набора команд Agner Fog: обратите внимание, что они могут не охватывать каждый угловой случай, например mov Числа -load могут быть только для 32/64-битных загрузок. Также обратите внимание, что значения задержки загрузки Agner Fog не являются задержкой загрузки из кэша L1D; они имеют смысл только как часть задержки сохранения / перезагрузки (переадресации магазина), но относительные числа скажут нам, сколько циклов movzx добавляет поверх mov (часто без лишних циклов).

  • P5 Pentium (в порядке исполнения): movzx -load - это 3-тактная инструкция (плюс узкое место декодирования из 0F префикс), против mov -нагрузки, являющиеся пропускной способностью одного цикла. (Они все еще имеют задержку, хотя).
  • Intel:
  • PPro / Pentium II / III: movzx / movsx работать только на порту загрузки, такой же пропускной способности, как обычный mov,
  • Core2 / Nehalem: то же самое, кроме movsxd r64, m на Нехалеме, по-видимому, нужен АЛУ (без микроплавкого предохранителя). Возможно, Core2 такой же, но Агнер этого не проверял.
  • Семья Sandybridge (SnB через Skylake и позже): movzx / movsx нагрузки однопроцессорные (просто порт загрузки) и выполняются идентично mov грузы.
  • Pentium4 (нетберт): movzx работает только на порту загрузки, такой же, как mov, movsx нагрузка + ALU, и занимает 1 дополнительный цикл.
  • Атом (по порядку): таблица Агнера неясна для источника памяти movzx / movsx нужен ALU, но они определенно быстрые. Номер латентности только для рег, рег.
  • Silvermont: так же, как Atom: быстро, но неясно, нужен ли порт.
  • KNL (на основе Silvermont): списки Агнера movzx / movsx с источником памяти, использующим IP0 (ALU), но задержка равна mov r,m так что нет штрафа. (Давление в исполнительном блоке не является проблемой, потому что декодеры KNL в любом случае едва могут поддерживать свои 2 ALU).

  • AMD:

  • Bobcat: movzx / movsx нагрузки 1 за такт, 5 циклов задержки. mov -нагрузка составляет 4с задержки.
  • Jaguar: movzx / movsx нагрузки - 1 за такт, задержка 4 цикла. mov нагрузки равны 1 на такт, задержка 3c для 32/64-битной или 4c для mov r8/r16, m (но все же только порт AGU, а не объединение ALU, как это делают Haswell/Skylake).
  • K7 / K8 / K10: movzx / movsx нагрузки имеют пропускную способность 2 на такт, задержка на 1 цикл выше, чем mov нагрузки. Они используют AGU и ALU.
  • Бульдозерная семья: такая же, как К10, но movsx -нагрузка имеет 5 циклов задержки. movzx -нагрузка имеет 4 цикла задержки, mov -нагрузка имеет 3 цикла задержки. Так что в теории это может быть меньше задержки mov cx, word [mem] а потом movsx eax, cx (1 цикл), если ложная зависимость от 16-битной mov load не требует дополнительного слияния ALU или создания переносимой зависимости для вашего цикла.
  • Ryzen: movzx / movsx загрузка выполняется только в порту загрузки, с той же задержкой, что и mov грузы.
  • С ПОМОЩЬЮ
  • Via Nano 2000/3000: movzx работает только на порту загрузки, с той же задержкой, что и mov грузы. movsx это LD + ALU, с дополнительной задержкой 1с.

Когда я говорю "выполнять одинаково", я имею в виду не считать каких-либо частичных регистров штрафов или разбиений строки кэша от более широкой загрузки. например movzx eax, word [rsi] избегает слияния штраф против mov ax, word [rsi] на Skylake, но я все еще скажу, что mov выполняет идентично movzx, (Я думаю, я имею в виду, что mov eax, dword [rsi] без каких-либо разделений строки кэша так же быстро, как movzx eax, word [rsi].)


xor - обнуление полного регистра перед записью 16-разрядного регистра позволяет избежать более поздней остановки слияния частичных регистров в семействе Intel P6, а также нарушить ложные зависимости.

Если вы хотите хорошо работать и на P5, это может быть несколько лучше, но не намного хуже на любых современных процессорах, кроме PPro-PIII, где xor -zeroing не вызывает прерывания, даже если он все еще распознается как идиома обнуления, делающая EAX эквивалентной AX (нет частичной регистрации регистра при чтении EAX после записи AL или AX).

;; Probably not a good idea, maybe not faster on anything.

;mov  eax, 0             ; some code tuned for PIII used *both* this and xor-zeroing.
xor   eax, eax           ; *not* dep-breaking on early P6 (up to PIII)
mov    ax, word [src1]
cmp    ax, word [src2]

; safe to read EAX without partial-reg stalls

Префикс размера операнда не идеален для P5, поэтому вы можете рассмотреть возможность использования 32-битной загрузки, если вы уверены, что он не сбоит, пересекает границу строки кэша или вызывает сбой пересылки из-за недавнего 16-битный магазин.

На самом деле, я думаю, что 16-битный mov На Pentium загрузка может быть медленнее, чем movzx / cmp 2 последовательности команд. Похоже, что для работы с 16-битными данными так же эффективно, как и с 32-битными, не существует! (Конечно, кроме упакованного MMX).

См. Руководство Agner Fog для деталей Pentium, но префикс размера операнда занимает дополнительные 2 цикла для декодирования на P1 (оригинал P5) и PMMX, так что эта последовательность может фактически быть хуже, чем movzx нагрузки. На P1 (но не PMMX) 0F escape-байт (используется movzx) также считается префиксом, принимая дополнительный цикл для декодирования.

По-видимому movzx не в любом случае. Multi-цикл movzx будет скрывать задержку декодирования cmp ax, [src2], так movzx / cmp вероятно, все еще лучший выбор. Или график инструкции, чтобы movzx сделано раньше и cmp может может быть что-то в паре с В любом случае, правила планирования довольно сложны для P1/PMMX.


Я рассчитал этот цикл на Core2 (Conroe), чтобы доказать, что обнуление по xor позволяет избежать частичных остановок регистров для 16-битных регистров, а также low-8 (как для setcc al):

mov     ebp, 100000000
ALIGN 32
.loop:
%rep 4
    xor   eax, eax
;    mov   eax, 1234    ; just break dep on the old value, not a zeroing idiom
    mov   ax, cx        ; write AX
    mov   edx, eax      ; read EAX
%endrep

    dec   ebp           ; Core2 can't fuse dec / jcc even in 32-bit mode
    jg   .loop          ; but SnB does

perf stat -r4 ./testloop вывод для этого в статическом двоичном файле, который делает системный вызов sys_exit после:

 ;; Core2 (Conroe) with   XOR eax, eax
       469,277,071      cycles                    #    2.396 GHz
     1,400,878,601      instructions              #    2.98  insns per cycle
       100,156,594      branches                  #  511.462 M/sec
             9,624      branch-misses             #    0.01% of all branches

       0.196930345 seconds time elapsed                                          ( +-  0.23% )

2,98 инструкций за цикл имеет смысл: 3 порта ALU, все инструкции ALU, и нет макросов, поэтому каждый из них равен 1 моп. Таким образом, мы работаем на 3/4 от входной мощности. Цикл имеет 3*4 + 2 инструкции / упс.

На Core2 все сильно отличается xor обнуление прокомментировал и используя mov eax, imm32 вместо:

 ;; Core2 (Conroe) with   MOV eax, 1234
 1,553,478,677      cycles                    #    2.392 GHz
 1,401,444,906      instructions              #    0.90  insns per cycle
   100,263,580      branches                  #  154.364 M/sec
        15,769      branch-misses             #    0.02% of all branches

   0.653634874 seconds time elapsed                                          ( +-  0.19% )

0,9 IPC (по сравнению с 3) соответствует внешнему сбою в течение 2–3 циклов для вставки слияния в каждую mov edx, eax,

Skylake запускает обе петли одинаково, потому что mov eax,imm32 все еще разрушает зависимость. (Как и большинство инструкций с назначением только для записи, но остерегайтесь ложных зависимостей от popcnt а также lzcnt / tzcnt).

На самом деле, uops_executed.thread Счетчик perf показывает разницу: в SnB-семействе обнуление xor не требует выполнения, потому что обрабатывается на этапе выпуска / переименования. (mov edx,eax также устраняется при переименовании, так что количество мопов на самом деле довольно мало). Количество циклов одинаково с точностью до 1% в любом случае.

 ;;; Skylake (i7-6700k) with xor-zeroing
 Performance counter stats for './testloop' (4 runs):

         84.257964      task-clock (msec)         #    0.998 CPUs utilized            ( +-  0.21% )
                 0      context-switches          #    0.006 K/sec                    ( +- 57.74% )
                 0      cpu-migrations            #    0.000 K/sec                  
                 3      page-faults               #    0.036 K/sec                  
       328,337,097      cycles                    #    3.897 GHz                      ( +-  0.21% )
       100,034,686      branches                  # 1187.243 M/sec                    ( +-  0.00% )
     1,400,195,109      instructions              #    4.26  insn per cycle           ( +-  0.00% )  ## dec/jg fuses into 1 uop
     1,300,325,848      uops_issued_any           # 15432.676 M/sec                   ( +-  0.00% )    ###   fused-domain
       500,323,306      uops_executed_thread      # 5937.994 M/sec                    ( +-  0.00% )    ### unfused-domain
                 0      lsd_uops                  #    0.000 K/sec                  

       0.084390201 seconds time elapsed                                          ( +-  0.22% )

lsd.uops равен нулю, потому что буфер цикла отключен обновлением микрокода. Это узкие места на входе: uops (fused-domain) / clock = 3.960 (из 4). Этот последний.04 может быть частично перегружен ОС (прерывания и т. Д.), Потому что это только подсчет мопов пользовательского пространства.

Придерживаться 32-битного режима и использовать 16-битные инструкции

mov eax, 0         ; clear the register
mov ax, 10-binary  ; do 16 bit stuff

В качестве альтернативы, я думаю, я мог бы переместить 4 байта в регистр, а затем каким-то образом CMP только два из них

mov eax, xxxx ; 32 bit num loaded
mov ebx, xxxx
cmp ax, bx    ; 16 bit cmp performed in 32 bit mode
Другие вопросы по тегам