Ответ 1
Для большинства архитектур с инструкциями фиксированной ширины ответом, вероятно, будет скучная инструкция mov
немедленного расширения или инвертирования знака или пары mov lo/high. например на ARM, mvn r0, #0
(не двигаться). Смотрите вывод gcc asm для x86, ARM, ARM64 и MIPS, в проводнике компилятора Godbolt. IDK что-нибудь о zseries asm или машинном коде.
В ARM eor r0,r0,r0
значительно хуже, чем mov -immediate. Это зависит от старого значения, без специальной обработки. Правила упорядочения зависимостей в памяти не позволяют ARM uarch использовать его в специальном регистре, даже если они этого хотят. То же самое относится к большинству других RISC ISA со слабо упорядоченной памятью, но для которых не требуются барьеры для memory_order_consume
( в терминологии С++ 11).
x86 xor-zeroing является особенным из-за его набора команд переменной длины.
Исторически 8086 xor ax,ax
был быстрым напрямую, потому что он был маленьким. Поскольку эта идиома стала широко использоваться (и обнуление встречается гораздо чаще, чем все), разработчики процессоров оказали ей особую поддержку, и теперь xor eax,eax
быстрее, чем mov eax,0
в семействе Intel Sandybridge и некоторых других процессорах, даже без учета прямые и косвенные эффекты размера кода. См. Как лучше всего установить регистр в ноль в сборке x86: xor, mov или и?, чтобы узнать о преимуществах микроархитектуры, которые мне удалось выкопать.
Если бы у x86 был набор инструкций фиксированной ширины, интересно, получил бы mov reg, 0
такой же особый режим, как и при обнулении xor? Возможно, потому что нарушение зависимости перед записью low8 или low16 важно.
Стандартные параметры для лучшей производительности:
mov eax, -1
: 5 байтов, используя кодировкуmov r32, imm32
. (К сожалению, расширение TG410 отсутствует). Отличная производительность на всех процессорах. 6 байтов для r8-r15 (префикс REX).mov rax, -1
: 7 байтов, используя кодировкуmov r/m64, sign-extended-imm32
. (Не версия REX.W = 1 версииeax
. Это будет 10-байтовыйmov r64, imm64
). Отличная производительность на всех процессорах.
Странные варианты, которые сохраняют некоторый размер кода, обычно за счет производительности:
xor eax,eax
/dec rax
(илиnot rax
): 5 байтов (4 для 32-разрядныхeax
). Недостаток: два мопа для внешнего интерфейса. Все еще только одно неиспользуемое UOP домена для планировщика/исполнительных модулей на недавнем Intel, где xor-zeroing обрабатывается во внешнем интерфейсе.mov
-immediate всегда нужен исполнительный блок. (Но целочисленная пропускная способность ALU редко является узким местом для инструкций, которые могут использовать любой порт; проблема в дополнительном входном давлении)xor ecx,ecx
/lea eax, [rcx-1]
Всего 5 байтов для 2 констант (6 байтов дляrax
): оставляет отдельный обнуленный регистр. Если вы уже хотите обнулить регистр, то у этого недостатка почти нет.lea
может работать на меньшем количестве портов, чемmov r,i
, на большинстве процессоров, но, поскольку это начало новой цепочки зависимостей, центральный процессор может запустить его в любом цикле резервного порта выполнения после того, как он выдаст ошибку.Тот же трюк работает для любых двух соседних констант, если вы делаете первый с
mov reg, imm32
, а второй сlea r32, [base + disp8]
. disp8 имеет диапазон от -128 до +127, в противном случае вам нуженdisp32
.or eax, -1
: 3 байта (4 дляrax
) с использованием кодировкиor r/m32, sign-extended-imm8
. Недостаток: ложная зависимость от старого значения регистра.push -1
/pop rax
: 3 байта. Медленно, но мало. Рекомендуется только для эксплойтов/код-гольфа. Работает для любого sign-extended-imm8, в отличие от большинства других.МИНУСЫ:
- использует блоки сохранения и загрузки, а не ALU. (Возможно, преимущество в пропускной способности в редких случаях в семействе AMD Bulldozer, где есть только два целочисленных канала выполнения, но пропускная способность декодирования/выпуска/вывода выше, чем у этого. Но не пытайтесь сделать это без тестирования.)
- задержка сохранения/перезагрузки означает, что
rax
не будет готов к ~ 5 циклам, например, после этого на Skylake. - (Intel): переводит стековый движок в режим, модифицированный rsp, поэтому в следующий раз, когда вы прочитаете
rsp
напрямую, он выполнит синхронизацию стека. (например, дляadd rsp, 28
или дляmov eax, [rsp+8]
). - Магазин может отсутствовать в кеше, вызывая дополнительный трафик памяти. (Возможно, если вы не касались стека внутри длинного цикла).
Векторные регистры разные
Установка векторных регистров на единичные с помощью pcmpeqd xmm0,xmm0
имеет особый случай на большинстве процессоров как нарушение зависимости (не Silvermont/KNL), но все еще нуждается в исполнительном модуле, чтобы фактически записать их. pcmpeqb/w/d/q
все работает, но q
медленнее на некоторых процессорах.
Для AVX2, ymm
эквивалент vpcmpeqd ymm0, ymm0, ymm0
также является лучшим выбором.
Для AVX без AVX2 выбор менее очевиден: не существует единственного очевидного лучшего подхода. Компиляторы используют различные стратегии: gcc предпочитает загружать 32-байтовую константу с vmovdqa
, в то время как более старый clang использует 128-битный vpcmpeqd
, за которым следует перекрестная линия vinsertf128
, чтобы заполнить верхнюю половину. Более новый кланг использует vxorps
для обнуления регистра, а затем vcmptrueps
для его заполнения. Это моральный эквивалент подхода vpcmpeqd
, но vxorps
необходим для устранения зависимости от предыдущей версии регистра, а задержка vcmptrueps
равна 3. Это разумный выбор по умолчанию.
Выполнение vbroadcastss
из 32-битного значения, вероятно, строго лучше, чем подход с загрузкой, но сложно заставить компиляторы генерировать это.
Лучший подход, вероятно, зависит от окружающего кода.
Самый быстрый способ установить значение __m256 для всех ОДИН битов
Сравнения AVX512 доступны только с регистром маски (например, k0
) в качестве места назначения, поэтому в настоящее время компиляторы используют vpternlogd zmm0,zmm0,zmm0, 0xff
в качестве идиомы "все единицы" 512b. (0xff делает каждый элемент таблицы истинности с 3 входами 1
). Это не является специальным случаем, как нарушение зависимости на KNL или SKL, но имеет пропускную способность 2 на тактовую частоту на Skylake-AVX512. Это лучше, чем использование более узких AVX-устройств, разбивающих зависимости, и их трансляция или перетасовка.
Если вам нужно заново сгенерировать все единицы внутри цикла, очевидно, что наиболее эффективный способ - использовать vmov*
для копирования регистра всех единиц. Это даже не использует исполнительный модуль на современных процессорах (но все же требует пропускной способности внешнего интерфейса). Но если у вас нет векторных регистров, загрузка константы или [v]pcmpeq[b/w/d]
- хороший выбор.
Для AVX512 стоит попробовать VPMOVM2D zmm0, k0
или, может быть, VPBROADCASTD zmm0, eax
. Каждый из них имеет пропускную способность только 1с, но они должны нарушать зависимости от старого значения zmm0 (в отличие от vpternlogd
). Им требуется маска или регистр целых чисел, который вы инициализировали вне цикла с помощью kxnorw k1,k0,k0
или mov eax, -1
.
Для регистров маски AVX512, kxnorw k1,k0,k0
работает, но это не нарушение зависимости от текущих процессоров. Руководство по оптимизации Intel предлагает использовать его для генерации единиц перед командой сбора, но рекомендует избегать использования того же входного регистра, что и для вывода. Это позволяет избежать зависимости, независимой от других сборок, от предыдущей в цикле. Поскольку k0
часто не используется, его обычно удобно читать.
Я думаю, что vpcmpeqd k1, zmm0,zmm0
будет работать, но он, вероятно, не имеет специального случая как идиома k0 = 1 без зависимости от zmm0. (Чтобы установить все 64 бита вместо 16 младших, используйте AVX512BW vpcmpeqb
)
На Skylake-AVX512 инструкции k
, которые работают с регистрами маски , выполняются только на одном порту, даже на таких простых, как kandw
. (Также обратите внимание, что Skylake-AVX512 не будет запускать векторные мопы на порте 1, когда в канале есть какие-либо 512-битные операции, поэтому пропускная способность исполнительного модуля может стать настоящим узким местом.)
Нет kmov k0, imm
, только ходы из целого числа или памяти. Вероятно, нет инструкций k
, в которых то же самое определяется как специальное, поэтому оборудование на этапе выпуска/переименования не ищет его для регистров k
.