Ответ 1
В декодерах и uop-кеше режим адресации не влияет на микро-fusion (за исключением того, что команда с непосредственным операндом не может скомпоновать RIP-относительный режим адресации).
Но некоторые комбинации режима uop и адресации не могут оставаться микро-сплавленными в ROB (в ядре вне порядка), поэтому процессоры Intel SnB-семейства "не ламинируют", когда это необходимо, в какой-то момент этап выпуска/переименования. Для размера пропускной способности и размера окна вне порядка (размер ROB) важно, чтобы процент упсонов с плавной долей после не-ламинирования был важным.
Руководство по оптимизации Intel описывает неламинирование для Sandybridge в разделе 2.3.2.4: Micro-op Queue и Loop Stream Detector (LSD), но не описывает изменения для последующих микроархитектур.
Правила, насколько я могу судить по экспериментам на SnB, HSW и SKL:
- SnB (и я предполагаю также IvB): индексированные режимы адресации всегда не ламинируются, другие остаются микроплавками. IACA (в основном?) Правильный.
- HSW, SKL: они содержат только индексированную инструкцию ALU с микросоединением, если она имеет 2 операнда и обрабатывает регистр dst как read-modify-write. Здесь "операнды" включают в себя флаги, означающие, что
adc
иcmov
не являются микро-предохранителями. Большинство VEX-закодированных инструкций также не сливаются, поскольку они обычно имеют три операнда (поэтомуpaddb xmm0, [rdi+rbx]
плавкие предохранители, ноvpaddb xmm0, xmm0, [rdi+rbx]
нет). Наконец, случайная инструкция с двумя операндами, где только первый операнд пишет только, напримерpabsb xmm0, [rax + rbx]
, также не сливается. IACA ошибается, применяя правила SnB.
Связанные: простые (неиндексированные) режимы адресации являются единственными, которые могут обрабатывать выделенный блок хранения-адреса на порту7 (Хасуэлл и позже), поэтому по-прежнему полезно избегать индексированных режимов адресации для магазинов. (Хорошим трюком для этого является обращение к вашему dst с помощью одного регистра, но src с dst+(initial_src-initial_dst)
. Тогда вам нужно только увеличить регистр dst внутри цикла.)
Обратите внимание, что некоторые инструкции никогда не микро-предохранители вообще (даже в декодерах/uop-кеш). например shufps xmm, [mem], imm8
, или vinsertf128 ymm, ymm, [mem], imm8
, всегда являются 2 uops на SnB через Skylake, хотя их версии с реестром-исходным кодом составляют всего 1 мкп. Это типично для инструкций с управляющим операндом imm8 плюс обычные операнды register/memory dest/src1, src2, но есть несколько других случаев. например PSRLW/D/Q xmm,[mem]
(счетчик сдвига вектора из операнда памяти) не замачивается, а также не PMULLD.
См. также этот пост в блоге Agner Fog для обсуждения ограничений пропускной способности выпуска на HSW/SKL, когда вы читаете множество регистров: Многое микро-слияние с режимами индексированной адресации может привести к замедлению по сравнению с теми же инструкциями с меньшим количеством операндов регистров: режимы однорежимной адресации и немедленно. Мы еще не знаем причину, но я подозреваю, что какой-то вид предела чтения регистра, возможно, связано с чтением большого количества холодных регистров из ПРФ.
Тестовые примеры, цифры от реальных измерений: все эти микроплавкие в декодерах, AFAIK, даже если они позже не ламинируются.
# store
mov [rax], edi SnB/HSW/SKL: 1 fused-domain, 2 unfused. The store-address uop can run on port7.
mov [rax+rsi], edi SnB: unlaminated. HSW/SKL: stays micro-fused. (The store-address can't use port7, though).
mov [buf +rax*4], edi SnB: unlaminated. HSW/SKL: stays micro-fused.
# normal ALU stuff
add edx, [rsp+rsi] SnB: unlaminated. HSW/SKL: stays micro-fused.
# I assume the majority of traditional/normal ALU insns are like add
Инструкции с тремя входами, которые HSW/SKL, возможно, придется не ламинировать
vfmadd213ps xmm0,xmm0,[rel buf] HSW/SKL: stays micro-fused: 1 fused, 2 unfused.
vfmadd213ps xmm0,xmm0,[rdi] HSW/SKL: stays micro-fused
vfmadd213ps xmm0,xmm0,[0+rdi*4] HSW/SKL: un-laminated: 2 uops in fused & unfused-domains.
(So indexed addressing mode is still the condition for HSW/SKL, same as documented by Intel for SnB)
# no idea why this one-source BMI2 instruction is unlaminated
# It different from ADD in that its destination is write-only (and it uses a VEX encoding)
blsi edi, [rdi] HSW/SKL: 1 fused-domain, 2 unfused.
blsi edi, [rdi+rsi] HSW/SKL: 2 fused & unfused-domain.
adc eax, [rdi] same as cmov r, [rdi]
cmove ebx, [rdi] Stays micro-fused. (SnB?)/HSW: 2 fused-domain, 3 unfused domain.
SKL: 1 fused-domain, 2 unfused.
# I haven't confirmed that this micro-fuses in the decoders, but I'm assuming it does since a one-register addressing mode does.
adc eax, [rdi+rsi] same as cmov r, [rdi+rsi]
cmove ebx, [rdi+rax] SnB: untested, probably 3 fused&unfused-domain.
HSW: un-laminated to 3 fused&unfused-domain.
SKL: un-laminated to 2 fused&unfused-domain.
Я предполагаю, что Бродвелл ведет себя как Skylake для adc/cmov.
Странно, что HSW не ламинирует АЦП с памятью и CMOV. Возможно, Intel не оборачивается, чтобы изменить это от SnB до того, как они наберут крайний срок для доставки Haswell.
В таблице Agner insn говорится, что cmovcc r,m
и adc r,m
вообще не замаскированы на HSW/SKL, но это не соответствует моим экспериментам. Количество циклов, в которые я измеряю, совпадает с количеством ошибок uop для объединенных доменов, для узкого места с 4-мя часами/часами. Надеюсь, он дважды проверит это и исправьте таблицы.
Целое число памяти ALU:
add [rdi], eax SnB: untested (Agner says 2 fused-domain, 4 unfused-domain (load + ALU + store-address + store-data)
HSW/SKL: 2 fused-domain, 4 unfused.
add [rdi+rsi], eax SnB: untested, probably 4 fused & unfused-domain
HSW/SKL: 3 fused-domain, 4 unfused. (I don't know which uop stays fused).
HSW: About 0.95 cycles extra store-forwarding latency vs. [rdi] for the same address used repeatedly. (6.98c per iter, up from 6.04c for [rdi])
SKL: 0.02c extra latency (5.45c per iter, up from 5.43c for [rdi]), again in a tiny loop with dec ecx/jnz
adc [rdi], eax SnB: untested
HSW: 4 fused-domain, 6 unfused-domain. (same-address throughput 7.23c with dec, 7.19c with sub ecx,1)
SKL: 4 fused-domain, 6 unfused-domain. (same-address throughput ~5.25c with dec, 5.28c with sub)
adc [rdi+rsi], eax SnB: untested
HSW: 5 fused-domain, 6 unfused-domain. (same-address throughput = 7.03c)
SKL: 5 fused-domain, 6 unfused-domain. (same-address throughput = ~5.4c with sub ecx,1 for the loop branch, or 5.23c with dec ecx for the loop branch.)
Да, это право, adc [rdi],eax
/dec ecx
/jnz
работает быстрее, чем тот же цикл с add
вместо adc
в SKL. Я не пытался использовать разные адреса, так как ясно, что SKL не любит повторные перезаписи одного и того же адреса (латентность пересылки в хранилище выше ожидаемой. См. Также this сообщение о повторном хранении/перезагрузке на тот же адрес, который медленнее, чем ожидалось, в SKL.
Память-назначения adc
- это так много, потому что семейство Intel P6 (и, по-видимому, SnB-семейство) не может хранить одни и те же записи TLB для всех uops команды multi-uop, поэтому нужен дополнительный uop для решения проблемного случая, когда загрузка и добавление завершаются, а затем ошибки хранилища, но insn нельзя просто перезапустить, потому что CF уже обновлен. Интересная серия комментариев Энди Глева (@krazyglew).
Предположительно, слияние в декодерах и без ламинирования позже избавит нас от необходимости микрокода ROM, чтобы создать более 4 объединенных доменов из одной команды для adc [base+idx], reg
.
Почему SnB-семья не ламинирует:
Sandybridge упростил внутренний формат uop для экономии энергии и транзисторов (наряду с существенным изменением использования файла физического регистра, вместо того, чтобы хранить данные ввода/вывода в ROB). Сетевые процессоры SnB допускают только ограниченное количество входных регистров для uop с плавным доменом в ядре вне порядка. Для SnB/IvB этот предел - 2 входа (включая флаги). Для HSW и позже предел составляет 3 входа для uop. Я не уверен, что использование памяти-назначения add
и adc
в полной мере использует это, или если Intel должна была заставить Хасуэлл выйти из двери с некоторыми инструкциями
Nehalem и ранее имеют предел в 2 входа для uop неустановленной области, но ROB, по-видимому, может отслеживать микро-fused uops с 3 входными регистрами (операндом, базой и индексом без памяти).
Таким образом, индексированные магазины и инструкции по загрузке ALU + могут эффективно декодироваться (не обязательно быть первым uop в группе) и не занимать лишнее пространство в кэше uop, но в остальном преимущества микро-слияния в основном пошли для настройки плотных петель. "не-ламинирование" происходит до того, как 4-fused-domain-uops-per-cycle выдает/удаляет ширину из-за отсутствия ядра. Счетчики производительности с плавным доменом (uops_issued/uops_retired.retire_slots) подсчитывают количество подключенных доменов после отказа.
Intel-описание переименования (раздел 2.3.3.1: Renamer) подразумевает, что это этап выпуска/переименования, который на самом деле делает неламинирование, поэтому uops, предназначенные для не-ламинирования, все еще могут быть микроплавками в 28/56/64 queue queue/loop-buffer foo-domain (также известный как IDQ).
TODO: проверьте это. Создайте цикл, который должен просто сместиться в буфере цикла. Измените что-нибудь, чтобы один из uops был нерасширен до выдачи, и посмотрите, все ли он выполняется из буфера цикла (LSD), или если все uops теперь повторно выбраны из кеша uop (DSB). Есть перфорированные счетчики, чтобы отслеживать, откуда приходит uops, поэтому это должно быть легко.
Harder TODO: если между чтением из кеша uop и добавлением IDQ происходит неламинирование, проверьте, может ли он когда-либо уменьшить пропускную способность uop-cache. Или, если на стадии эмиссии не происходит ламинация, может ли это повредить пропускную способность? (то есть как он обрабатывает оставшиеся uops после выдачи первых 4.)
(см. предыдущую версию этого ответа для некоторых догадок, основанных на настройке некоторого кода LUT, причем некоторые примечания на vpgatherdd
составляют около 1.7x больше циклов, чем цикл pinsrw
.)
Экспериментальное тестирование на SnB
Номера HSW/SKL были измерены на i5-4210U и i7-6700k. Оба поддерживали HT (но система простаивала, так что поток имел все ядро для себя). Я запускал те же статические двоичные файлы в обеих системах, Linux 4.10 на SKL и Linux 4.8 на HSW, используя ocperf.py
. (Ноутбук HSW NFS установлен на моем рабочем столе SKL/home.)
Номера SnB были измерены, как описано ниже, на i5-2500k, который больше не работает.
Подтверждено тестированием с помощью счетчиков производительности для циклов и циклов.
Я нашел таблицу событий PMU для Intel Sandybridge, для использования с командой Linux perf
. (Стандарт perf
, к сожалению, не имеет символических имен для большинства аппаратных событий PMU, таких как uops.) Я использовал его для недавнего ответа.
ocperf.py
предоставляет символические имена для этих событий PMU, связанных с uarch, поэтому вам не нужно искать таблицы. Кроме того, одно и то же символическое имя работает через несколько урчей. Я не знал об этом, когда впервые написал этот ответ.
Чтобы проверить микропроцессор uop, я построил тестовую программу, которая была узкополостной на пределе FED-диапазона для процессоров Intel. Чтобы избежать конкуренции с исполнением порта, многие из этих uops составляют nop
s, которые все еще находятся в кэше uop и проходят через конвейер так же, как и любой другой uop, за исключением того, что они не отправляются в порт выполнения. (An xor x, same
или исключенный ход будут одинаковыми.)
Программа тестирования: yasm -f elf64 uop-test.s && ld uop-test.o -o uop-test
GLOBAL _start
_start:
xor eax, eax
xor ebx, ebx
xor edx, edx
xor edi, edi
lea rsi, [rel mydata] ; load pointer
mov ecx, 10000000
cmp dword [rsp], 2 ; argc >= 2
jge .loop_2reg
ALIGN 32
.loop_1reg:
or eax, [rsi + 0]
or ebx, [rsi + 4]
dec ecx
nop
nop
nop
nop
jg .loop_1reg
; xchg r8, r9 ; no effect on flags; decided to use NOPs instead
jmp .out
ALIGN 32
.loop_2reg:
or eax, [rsi + 0 + rdi]
or ebx, [rsi + 4 + rdi]
dec ecx
nop
nop
nop
nop
jg .loop_2reg
.out:
xor edi, edi
mov eax, 231 ; exit(0)
syscall
SECTION .rodata
mydata:
db 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
Я также обнаружил, что ширина канала uop из буфера цикла не является постоянной 4 за цикл, если цикл не является кратным 4 мкп. (т.е. abc
, abc
,..., not abca
, bcab
,...). Agner Fog microarch doc, к сожалению, не был ясен в этом ограничении буфера цикла. См. Снижается ли производительность при выполнении циклов, чей счетчик uop не является кратным ширине процессора? для более подробного изучения HSW/SKL. В этом случае SnB может быть хуже HSW, но я не уверен, и у меня пока нет работы с оборудованием SnB.
Я хотел сохранить макро-fusion (compare-and-branch) из изображения, поэтому я использовал nop
между dec
и веткой. Я использовал 4 nop
s, поэтому с микро-слиянием цикл был бы 8 uops и заполнял бы трубопровод с 2 циклами на 1 итерацию.
В другой версии цикла, используя режимы адресации с двумя операндами, которые не имеют микро-предохранителей, цикл будет состоять из 10 плагированных доменов и будет выполняться в 3 цикла.
Результаты моего 3,3-гигабайтного Intel Sandybridge (i5 2500k). Я ничего не делал, чтобы заставить контроллер cpufreq увеличивать тактовую частоту до тестирования, поскольку циклы - это циклы, когда вы не взаимодействуя с памятью. Я добавил аннотации к событиям счетчика производительности, которые я должен был ввести в hex.
тестирование режима адресации 1-reg: no cmdline arg
$ perf stat -e task-clock,cycles,instructions,r1b1,r10e,r2c2,r1c2,stalled-cycles-frontend,stalled-cycles-backend ./uop-test
Performance counter stats for './uop-test':
11.489620 task-clock (msec) # 0.961 CPUs utilized
20,288,530 cycles # 1.766 GHz
80,082,993 instructions # 3.95 insns per cycle
# 0.00 stalled cycles per insn
60,190,182 r1b1 ; UOPS_DISPATCHED: (unfused-domain. 1->umask 02 -> uops sent to execution ports from this thread)
80,203,853 r10e ; UOPS_ISSUED: fused-domain
80,118,315 r2c2 ; UOPS_RETIRED: retirement slots used (fused-domain)
100,136,097 r1c2 ; UOPS_RETIRED: ALL (unfused-domain)
220,440 stalled-cycles-frontend # 1.09% frontend cycles idle
193,887 stalled-cycles-backend # 0.96% backend cycles idle
0.011949917 seconds time elapsed
тестирование режима адресации 2-reg: с помощью cmdline arg
$ perf stat -e task-clock,cycles,instructions,r1b1,r10e,r2c2,r1c2,stalled-cycles-frontend,stalled-cycles-backend ./uop-test x
Performance counter stats for './uop-test x':
18.756134 task-clock (msec) # 0.981 CPUs utilized
30,377,306 cycles # 1.620 GHz
80,105,553 instructions # 2.64 insns per cycle
# 0.01 stalled cycles per insn
60,218,693 r1b1 ; UOPS_DISPATCHED: (unfused-domain. 1->umask 02 -> uops sent to execution ports from this thread)
100,224,654 r10e ; UOPS_ISSUED: fused-domain
100,148,591 r2c2 ; UOPS_RETIRED: retirement slots used (fused-domain)
100,172,151 r1c2 ; UOPS_RETIRED: ALL (unfused-domain)
307,712 stalled-cycles-frontend # 1.01% frontend cycles idle
1,100,168 stalled-cycles-backend # 3.62% backend cycles idle
0.019114911 seconds time elapsed
Итак, обе версии выполнили 80M-инструкции и отправили 60M-юикс в порты выполнения. (or
с источником памяти отправляется в ALU для or
и порт загрузки для нагрузки, независимо от того, был ли он микроконфигурирован или нет в остальной части конвейера. nop
не отправляет к порту выполнения вообще.) Аналогично, обе версии удаляют 100M недопустимых доменов, потому что здесь 40M nops.
Разница заключается в счетчиках для плавного домена.
- Версия адреса 1-регистра только выдает и удаляет удаленные модули 80M с плавным доменом. Это то же самое, что и количество инструкций. Каждый insn превращается в один uop с плавным доменом.
- В версии с 2-регистровыми адресами выдается 100-мегагерцовый домен. Это то же самое, что и число неудовлетворительных доменов, что указывает на то, что микро-слияние не произошло.
Я подозреваю, что вы видели только разницу между UOPS_ISSUED и UOPS_RETIRED (использовались слоты для выхода на пенсию), если неверные предсказания отрасли привели к отмене uops после выпуска, но до выхода на пенсию.
И, наконец, влияние производительности действительно. Неконсолированная версия занимала в 1,5 раза больше тактовых циклов. Это преувеличивает разницу в производительности по сравнению с большинством реальных случаев. Цикл должен выполняться в течение целого числа циклов, а два дополнительных ввода - от 2 до 3. Часто дополнительные 2-х платных доменов будут меньше. И потенциально никакой разницы, если код вставлен в бутылку чем-то другим, кроме 4-fused-domain-uops-per-cycle.
Тем не менее, код, который делает много ссылок на память в цикле, может быть более быстрым, если реализован с умеренным объемом разворачивания и увеличения количества указателей, которые используются с простой адресацией [base + immediate offset]
, вместо использования режимов адресации [base + index]
.
дальнейший материал
RIP-относительный с немедленным не может микро-предохранитель. Тест Agner Fog показывает, что это происходит даже в декодерах/uop-кешках, поэтому они никогда не сливаются в первую очередь (а не без ламинирования).
IACA ошибается и утверждает, что оба этих микроплавких предохранителя:
cmp dword [abs mydata], 0x1b ; fused counters != unfused counters (micro-fusion happened, and wasn't un-laminated). Uses 2 entries in the uop-cache, according to Agner Fog testing
cmp dword [rel mydata], 0x1b ; fused counters ~= unfused counters (micro-fusion didn't happen)
RIP-rel делает микро-предохранитель (и остается плавленным), когда нет немедленного, например:
or eax, dword [rel mydata] ; fused counters != unfused counters, i.e. micro-fusion happens
Микро-фьюжн не увеличивает латентность команды. Нагрузка может выдаваться до того, как будет готов другой вход.
ALIGN 32
.dep_fuse:
or eax, [rsi + 0]
or eax, [rsi + 0]
or eax, [rsi + 0]
or eax, [rsi + 0]
or eax, [rsi + 0]
dec ecx
jg .dep_fuse
Этот цикл работает с 5 циклами на итерацию из-за цепочки eax
dep. Не быстрее, чем последовательность or eax, [rsi + 0 + rdi]
или mov ebx, [rsi + 0 + rdi] / or eax, ebx
. (В неработающих версиях и в версиях mov
выполняется одинаковое количество uops.) Проверка расписания/удаления происходит в незанятом домене. Недавно выпущенные uops отправляются в планировщик (aka Reservation Station (RS)), а также ROB. Они оставляют планировщик после отправки (он также отправляется в исполнительный блок), но остаются в ROB до выхода на пенсию. Таким образом, окно отсутствия порядка для скрытия задержки загрузки составляет, по меньшей мере, размер планировщика (54 непроверенных доменов в Sandybridge, 60 в Хасуэле, 97 в Скайлаке).
Micro-fusion не имеет ярлыка для базы и смещения, являющегося одним и тем же регистром. Цикл с or eax, [mydata + rdi+4*rdi]
(где rdi обнуляется) выполняет столько циклов и циклов, сколько цикл с or eax, [rsi+rdi]
. Этот режим адресации можно использовать для итерации по массиву структур с нечетным размером, начиная с фиксированного адреса. Это, вероятно, никогда не используется в большинстве программ, поэтому неудивительно, что Intel не проводила транзисторы, позволяя этому специальному корпусу режимов с двумя регистрами использовать микро-предохранитель. (И Intel в любом случае документирует его как "индексированные режимы адресации", где необходим регистр и масштабный коэффициент.)
Макрослияние cmp
/jcc
или dec
/jcc
создает uop, который остается как один uop даже в нераспространенном домене. dec / nop / jge
может работать только в одном цикле, но вместо трех.