Является ли vxorps-обнуление на AMD Jaguar/Bulldozer/Zen быстрее с регистрами xmm, чем ymm?

Процессоры AMD обрабатывают 256-битные инструкции AVX путем декодирования на две операции 128b. например vaddps ymm0, ymm1,ymm1 на AMD Steamroller декодирует до 2 макроопсов с половиной пропускной способности vaddps xmm0, xmm1,xmm1.

XOR-обнуление - это особый случай (без входной зависимости и на Jaguar, по крайней мере, избегает потребления записи файла физического регистра и позволяет movdqa из этого регистра, который должен быть удален при выпуске/переименовании, например, Bulldozer делает все это время даже для не-zerod regs). Но обнаружено ли это достаточно рано, что vxorps ymm0,ymm0,ymm0 все еще только декодирует до 1 макрооператора с равной производительностью до vxorps xmm0,xmm0,xmm0? (в отличие от vxorps ymm3, ymm2,ymm1)

Или обнаружение независимости происходит позже, после того, как уже декодируется на два раза? Кроме того, вектор xor-zeroing на процессорах AMD по-прежнему использует порт выполнения? На процессорах Intel Nehalem нужен порт, но семейство Sandybridge обрабатывает его на этапе выпуска/переименования.

Таблицы команд Agner Fog не перечисляют этот особый случай, и его руководство по микроархиву не упоминает количество uops.


Это может означать, что vxorps xmm0,xmm0,xmm0 - лучший способ реализовать _mm256_setzero_ps().

Для AVX512 _mm512_setzero_ps() также сохраняет байты, используя, по возможности, только идиому обнуления VEX, а не EVEX. (т.е. для zmm0-15. vxorps xmm31,xmm31,xmm31 по-прежнему потребуется EVEX). gcc/clang в настоящее время используют икону с xor-zeroing любой необходимой ширины регистров, а не всегда используя AVX-128.

Сообщается как clang ошибка 32862 и gcc ошибка 80636. MSVC уже использует xmm. Еще не сообщается ICC, в котором также используются zmm regs для обнуления AVX512. (Хотя Intel, возможно, не захочет меняться, поскольку в настоящее время нет никакой выгоды для каких-либо процессоров Intel, только AMD. Если они когда-либо выпустят маломощный CPU, который разделяет векторы пополам, они могут. Их нынешний маломощный deisgn (Silvermont) t поддерживает AVX вообще, только SSE4.)


Единственный возможный недостаток, который я знаю в использовании инструкции AVX-128 для обнуления регистра 256b, заключается в том, что он не вызывает разминку исполнительных блоков 256b на процессорах Intel. Возможно, победить взлом C или С++, который пытается их согреть.

(256b векторных инструкций медленнее для первых циклов ~ 56k после первой команды 256b. См. раздел Skylake в Microarch pdf Agner Fog). Вероятно, это нормально, если вызов функции noinline, которая возвращает _mm256_setzero_ps, не является надежным способом разогрева исполнительных блоков. (Тот, который все еще работает без AVX2, и избегает любых нагрузок (которые могут кэшировать промах) __m128 onebits = _mm_castsi128_ps(_mm_set1_epi8(0xff));
return _mm256_insertf128_ps(_mm256_castps128_ps256(onebits), onebits), который должен компилироваться в pcmpeqd xmm0,xmm0,xmm0/vinsertf128 ymm0,xmm0,1. Это все еще довольно тривиально для того, что вы вызываете однажды, чтобы разогреть (или согреться) исполнительные блоки, намного опережающие критический цикл. И если вы хотите что-то, что может встроить, вам, вероятно, понадобится inline-asm.)


У меня нет оборудования AMD, поэтому я не могу проверить это.

Если у кого-то есть аппаратное обеспечение AMD, но он не знает, как тестировать, используйте счетчики perf для подсчета циклов (и предпочтительно m-ops или uops или что-то, что их называет AMD).

Это источник NASM/YASM, который я использую для проверки коротких последовательностей:

section .text
global _start
_start:

    mov     ecx, 250000000

align 32  ; shouldn't matter, but just in case
.loop:

    dec     ecx  ; prevent macro-fusion by separating this from jnz, to avoid differences on CPUs that can't macro-fuse

%rep 6
    ;    vxorps  xmm1, xmm1, xmm1
    vxorps  ymm1, ymm1, ymm1
%endrep

    jnz .loop

    xor edi,edi
    mov eax,231    ; exit_group(0) on x86-64 Linux
    syscall

Если вы не в Linux, возможно, замените материал после цикла (syscall) с помощью ret и вызовите функцию из функции C main().

Соберите с помощью nasm -felf64 vxor-zero.asm && ld -o vxor-zero vxor-zero.o, чтобы создать статический двоичный файл. (Или используйте asm-link script Я отправил в Q & A сборку статических/динамических двоичных файлов с/без libc).

Пример вывода на i7-6700k (Intel Skylake) на 3,9 ГГц. (IDK, почему моя машина работает только до 3,9 ГГц после того, как она простаивала несколько минут. Турбо до 4.2 или 4.4 ГГц работает нормально сразу после загрузки). Поскольку я использую перфорированные счетчики, на самом деле не имеет значения, какая тактовая частота работает на компьютере. Никаких нагрузок/хранилищ или пропусков кода-кеша не задействовано, поэтому количество циклов ядра-такта для всех является постоянным, независимо от того, как долго они находятся.

$ alias disas='objdump -drwC -Mintel'
$ b=vxor-zero;  asm-link "$b.asm" && disas "$b" && ocperf.py stat -etask-clock,cycles,instructions,branches,uops_issued.any,uops_retired.retire_slots,uops_executed.thread -r4 "./$b"
+ yasm -felf64 -Worphan-labels -gdwarf2 vxor-zero.asm
+ ld -o vxor-zero vxor-zero.o

vxor-zero:     file format elf64-x86-64


Disassembly of section .text:

0000000000400080 <_start>:
  400080:       b9 80 b2 e6 0e          mov    ecx,0xee6b280
  400085:       66 66 66 66 66 66 2e 0f 1f 84 00 00 00 00 00    data16 data16 data16 data16 data16 nop WORD PTR cs:[rax+rax*1+0x0]
  400094:       66 66 66 2e 0f 1f 84 00 00 00 00 00     data16 data16 nop WORD PTR cs:[rax+rax*1+0x0]

00000000004000a0 <_start.loop>:
  4000a0:       ff c9                   dec    ecx
  4000a2:       c5 f4 57 c9             vxorps ymm1,ymm1,ymm1
  4000a6:       c5 f4 57 c9             vxorps ymm1,ymm1,ymm1
  4000aa:       c5 f4 57 c9             vxorps ymm1,ymm1,ymm1
  4000ae:       c5 f4 57 c9             vxorps ymm1,ymm1,ymm1
  4000b2:       c5 f4 57 c9             vxorps ymm1,ymm1,ymm1
  4000b6:       c5 f4 57 c9             vxorps ymm1,ymm1,ymm1
  4000ba:       75 e4                   jne    4000a0 <_start.loop>
  4000bc:       31 ff                   xor    edi,edi
  4000be:       b8 e7 00 00 00          mov    eax,0xe7
  4000c3:       0f 05                   syscall

(ocperf.py is a wrapper with symbolic names for CPU-specific events.  It prints the perf command it actually ran):

perf stat -etask-clock,cycles,instructions,branches,cpu/event=0xe,umask=0x1,name=uops_issued_any/,cpu/event=0xc2,umask=0x2,name=uops_retired_retire_slots/,cpu/event=0xb1,umask=0x1,name=uops_executed_thread/ -r4 ./vxor-zero

 Performance counter stats for './vxor-zero' (4 runs):

        128.379226      task-clock:u (msec)       #    0.999 CPUs utilized            ( +-  0.07% )
       500,072,741      cycles:u                  #    3.895 GHz                      ( +-  0.01% )
     2,000,000,046      instructions:u            #    4.00  insn per cycle           ( +-  0.00% )
       250,000,040      branches:u                # 1947.356 M/sec                    ( +-  0.00% )
     2,000,012,004      uops_issued_any:u         # 15578.938 M/sec                   ( +-  0.00% )
     2,000,008,576      uops_retired_retire_slots:u # 15578.911 M/sec                   ( +-  0.00% )
       500,009,692      uops_executed_thread:u    # 3894.787 M/sec                    ( +-  0.00% )

       0.128516502 seconds time elapsed                                          ( +-  0.09% )

Материал + - 0,02% - это то, что я побежал perf stat -r4, поэтому он бинарно бивал 4 раза.

uops_issued_any и uops_retired_retire_slots являются плавленым доменом (предельный пропускной пропускной способностью 4 на часы в семействах Skylake и Bulldozer). Графы почти идентичны, потому что нет никаких неверных предсказаний ветки (которые приводят к отбрасыванию спекулятивно выпущенных uops вместо отставке).

uops_executed_thread - unops-domain uops (порты выполнения). xor-zeroing не нуждается в процессорах Intel, так что это просто деление и ветвь, которые фактически выполняются. (Если мы изменили операнды на vxorps, чтобы он не просто обнулял регистр, например vxorps ymm2, ymm1,ymm0, чтобы записать вывод в регистр, который следующий не читает, выполняемые uops будут соответствовать счету uop в объединенной домене. мы увидим, что предел пропускной способности составляет три vxorps за такт.)

2000 м. скомпилированные доменные модули, выпущенные в тактовых циклах 500 МБ, составляют 4,0 мкп за каждый такт: достигают теоретической максимальной пропускной способности интерфейса. 6 * 250 - 1500, поэтому эти подсчеты совпадают с декодированием Skylake vxorps ymm,ymm,ymm до 1 fused-domain uop.

С другим количеством циклов в цикле все не так хорошо. например петля 5 мкп, выпущенная только на 3,75 мкп за такт. Я намеренно выбрал это 8 uops (когда vxorps декодирует один-уп).

Ширина выдачи Zen составляет 6 мкп за цикл, поэтому может быть лучше с разным количеством разворачивания. (См. этот Q & A для получения более коротких циклов, чей счетчик uop не кратен ширине проблемы, на картах Intel SnB-семейства).

Ответ 1

xor'ing ymm register с самим собой генерирует два микрооперации на AMD Ryzen, а xor'ing xmm register с собой генерирует только один микрооператор. Таким образом, оптимальный способ обнуления ymm-регистра - это xor соответствующий регистр xmm с самим собой и полагаться на неявное расширение.

Единственный процессор, который поддерживает AVX512 сегодня, - это Knights Landing. Он использует один микрооператор для регистрации zmm. Очень часто приходится обрабатывать новое расширение векторного размера, разбивая его на две части. Это произошло с переходом от 64 до 128 бит и с переходом от 128 до 256 бит. Более чем вероятно, что некоторые процессоры в будущем (от AMD или Intel или любого другого поставщика) будут разделять 512-битные векторы на два 256-битных вектора или даже четыре 128-битных вектора. Таким образом, оптимальный путь к нулю регистра zmm - это к 128-битовому регистру с самим собой и полагаться на нулевое расширение. И вы правы, 128-битная VEX-кодированная инструкция на один или два байта короче.

Большинство процессоров распознают xor регистра, который сам по себе не зависит от предыдущего значения регистра.