Ответ 1
Другие ответы приветствуются, чтобы обратиться к Sandybridge и IvyBridge более подробно. У меня нет доступа к этому оборудованию.
Я не обнаружил каких-либо различий между HSW и SKL. На Haswell и Skylake все, что я тестировал до сих пор, поддерживает эту модель:
AL никогда не переименовывается отдельно от RAX (или r15b от r15). Поэтому, если вы никогда не касаетесь регистров high8 (AH/BH/CH/DH), все ведет себя точно так же, как на процессоре без переименования с частичной регистрацией (например, AMD).
Доступ только для записи к AL сливается с RAX с зависимостью от RAX. Для загрузок в AL это - микроплавленый ALU + load uop, который выполняется на p0156, что является одним из самых убедительных доказательств того, что он действительно объединяется при каждой записи, а не просто выполняет какую-то изощренную двойную бухгалтерию, как предположил Агнер.
Агнер (и Intel) говорят, что для Sandybridge может потребоваться объединенная мера для AL, поэтому он, вероятно, переименован отдельно от RAX. Для SnB в руководстве по оптимизации Intel (раздел 3.5.2.4 Частичные регистры) написано
SnB (не обязательно более поздняя версия) вставляет объединяющую меру в следующих случаях:
После записи в один из регистров AH, BH, CH или DH и до после чтения 2-, 4- или 8-байтовой формы того же регистра. В В этих случаях вставляется микрооперация. Вставка потребляет полный цикл распределения, в котором другие микрооперации не могут быть распределены.
После микрооперации с регистром назначения 1 или 2 байта, который не источник инструкции (или регистр большей формы), а перед последующим чтением 2-, 4- или 8-байтовой формы того же самого регистр. В этих случаях микрооперация слияния является частью потока.
Я думаю, они говорят, что на SnB add al,bl
будет RMW полностью RAX вместо того, чтобы переименовывать его отдельно, потому что один из исходных регистров является (частью) RAX. Я предполагаю, что это не относится к такой нагрузке, как mov al, [rbx + rax]
; rax
в режиме адресации, вероятно, не считается источником.
Я не проверял, должны ли high8 слияния все еще самостоятельно выпускать/переименовывать в HSW/SKL. Это сделало бы фронтальный удар эквивалентным 4 мопам (с тех пор проблема/переименование ширины конвейера).
- Нет способа разорвать зависимость с AL без написания EAX/RAX.
xor al,al
не помогает, как иmov al, 0
. movzx ebx, al
имеет нулевую задержку (переименована) и не нуждается в исполнительном блоке. (т.е. работы по устранению mov на HSW и SKL). Он запускает слияние AH, если он грязный, который, я думаю, необходим для того, чтобы он работал без ALU. Вероятно, это не совпадение с тем, что Intel отказалась от переименования в том же Uarch, который ввел mov-elission. (В руководстве по микроархиву Agner Fog говорится, что ходы с нулевым расширением не исключаются в HSW или SKL, только в IvB.)movzx eax, al
не устраняется при переименовании. MOV-ликвидации на Intel никогда не работает на то же самое, то же самое.mov rax,rax
также не устранен, хотя он не должен ничего расширять. (Хотя не было бы никакого смысла предоставлять ему специальную аппаратную поддержку, потому что это просто запрет, в отличие отmov eax,eax
). В любом случае, при расширении нуля предпочитайте перемещаться между двумя отдельными архитектурными регистрами, будь то 3-битный 2-mov
или 8-битныйmovzx
.movzx eax, bx
не устраняется при переименовании в HSW или SKL. Он имеет задержку 1С и использует ALU UOP. В руководстве по оптимизации Intel упоминается только нулевая задержка для 8-битного movzx (и указывается, чтоmovzx r32, high8
никогда не переименовывается).
Регистры с высокими значениями 8 могут быть переименованы отдельно от остальной части регистра, и они действительно требуют слияния.
- Доступ только для записи к
ah
с помощьюmov ah, reg8
илиmov ah, [mem8]
переименовывает AH, без зависимости от старого значения. Обе эти инструкции обычно не нуждаются в ALU-мопе для 3-битной версии 2-. (Ноmov ah, bl
не исключен; ему действительно необходим p0156 ALU, чтобы это могло быть совпадением). - RMW из AH (например,
inc ah
) загрязняет его. setcc ah
зависит от старогоah
, но все равно его загрязняет. Я думаю, чтоmov ah, imm8
такой же, но не проверял так много angular случаев.(Необъяснимо: цикл, включающий
setcc ah
, может иногда выполняться из ЛСД, см. циклrcr
в конце этого поста. Возможно, до тех пор, покаah
чист в конце цикла, он может использовать ЛСД?).Если
ah
грязный,setcc ah
сливается с переименованнымah
, вместо того, чтобы форсировать слияние сrax
. например%rep 4
(inc al
/test ebx,ebx
/setcc ah
/inc al
/inc ah
) не генерирует мопов слияния и работает только примерно в 8,7 с (задержка 8inc al
замедляется из-за конфликтов ресурсов из-за моп дляah
. Также для депоinc ah
/setcc ah
).Я думаю, что здесь происходит то, что
setcc r8
всегда реализован как чтение-изменение-запись. Вероятно, Intel решила, что не стоит делать мопsetcc
только для записи, чтобы оптимизировать случайsetcc ah
, поскольку это очень редко встречается для кода, сгенерированного компилятором, дляsetcc ah
. (Но смотрите ссылку на этот вопрос в вопросе: clang4.0 с-m32
сделает это.)reading AX, EAX, or RAX triggers a merge uop (which takes up front-end issue/rename bandwidth). Probably the RAT (Register Allocation Table) tracks the high-8-dirty state for the architectural R[ABCD]X, и even after a write to AH retires, the AH data is stored in a separate physical register from RAX. Even with 256 NOPs between writing AH и reading EAX, there is an extra merging uop. (ROB size=224 on SKL, so this guarantees that the
mov ah, 123
was retired). Detected with uops_issued/executed perf counters, which clearly show the difference.Чтение AX, EAX или RAX запускает объединение (которое занимает внешнюю проблему/переименовывает пропускную способность). Вероятно, RAT (таблица распределения регистров) отслеживает состояние с высоким уровнем загрязнения для архитектурного R [ABCD] X, и даже после прекращения записи в AH данные AH сохраняются в отдельном физическом регистре от RAX. Даже с 256 NOP между записью AH и чтением EAX, существует дополнительный слияние. (Размер ROB = 224 в SKL, так что это гарантирует, что
mov ah, 123
был удален). Обнаружено с помощью uops_issued/execute счетчиков перфорации, которые четко показывают разницу. Чтение-изменение-запись AL (например,inc al
) сливается бесплатно, как часть ALU UOP. (Тестируется только с несколькими простыми мопами, такими какadd
/inc
, но неdiv r8
илиmul r8
). Опять же, слияние не происходит, даже если AH грязный.Только для записи в EAX/RAX (например,
lea eax, [rsi + rcx]
илиxor eax,eax
) очищает грязное состояние AH (без слияния uop).- Только запись в AX (
mov ax, 1
) сначала вызывает слияние AH. Я думаю, что вместо специального случая это работает как любой другой RMW AX/RAX. (TODO: тестmov ax, bx
, хотя это не должно быть особенным, потому что он не переименован.) xor ah,ah
имеет задержку 1с, не вызывает прерывания и все еще нуждается в порте выполнения.- Чтение и/или запись AL не приводит к слиянию, поэтому AH может оставаться грязным (и использоваться независимо в отдельной цепочке развертывания). (например,
add ah, cl
/add al, dl
могут работать по 1 за такт (узкое место при дополнительной задержке).
Загрязнение AH предотвращает запуск цикла из LSD (буфер цикла), даже если нет слияний. LSD - это когда процессор перезагружает мопы в очереди, которая передает этап выпуска/переименования. (Называется IDQ).
Вставка объединяющихся мопов немного похожа на вставку стековых синхронизирующих мопов для механизма стеков. В руководстве по оптимизации Intel говорится, что SnB LSD не может запускать циклы с несовпадающими push
/pop
, что имеет смысл, но подразумевает, что он может запускать циклы со сбалансированным push
/pop
. Это не то, что я вижу в SKL: даже сбалансированный push
/pop
предотвращает запуск с LSD (например, push rax
/pop rdx
/times 6 imul rax, rdx
. (Может быть реальная разница между SnB LSD и HSW)/SKL: SnB может просто "заблокировать" мопы в IDQ вместо того, чтобы повторять их несколько раз, поэтому цикл из 5 мопов выдает 2 цикла вместо 1.25.) В любом случае, похоже, что HSW/SKL не может использовать LSD, когда регистр старшего разряда загрязнен или когда он содержит мопы стекового механизма.
Такое поведение может быть связано с ошибкой в SKL:
Проблема: В сложных микроархитектурных условиях короткие циклы из менее чем 64 команд, которые используют регистры AH, BH, CH или DH, а также соответствующие им более широкие регистры (например, RAX, EAX или AX для AH), могут вызвать непредсказуемое поведение системы., Это может произойти, только если оба логических процессора на одном физическом процессоре активны.
Это также может быть связано с инструкцией Intel по оптимизации, согласно которой SnB, по крайней мере, должен сам выпустить/переименовать операцию AH-merge в цикле. Это странная разница для внешнего интерфейса.
Мой журнал ядра Linux говорит microcode: sig=0x506e3, pf=0x2, revision=0x84
.
Пакет Arch Linux intel-ucode
просто предоставляет обновление, вы должны отредактировать файлы конфигурации, чтобы фактически загрузить его. Поэтому мой тест Skylake проводился на i7-6700k с ревизией микрокода 0x84, которая не включает исправление для SKL150. Это соответствует поведению Хэсвелла в каждом случае, который я проверял, IIRC. (например, и Haswell, и мой SKL могут запустить цикл setne ah
/add ah,ah
/rcr ebx,1
/mov eax,ebx
из LSD). У меня включен HT (что является предварительным условием для манифеста SKL150), но я тестировал в основном простаивающей системе, поэтому мой поток имел ядро для себя.
С обновленным микрокодом LSD полностью отключен на все время, а не только когда активны частичные регистры. lsd.uops
всегда точно равен нулю, в том числе для реальных программ, а не для синтетических циклов. Аппаратные ошибки (а не ошибки микрокода) часто требуют отключения целой функции для исправления. Вот почему сообщается, что у SKL-avx512 (SKX) нет буфера обратной связи. К счастью, это не проблема производительности: повышенная пропускная способность UL-кэша в SKL по сравнению с Broadwell почти всегда идет в ногу с проблемой/переименованием.
Дополнительная задержка AH/BH/CH/DH:
- Чтение AH, когда оно не загрязнено (переименовано отдельно), добавляет дополнительный цикл задержки для обоих операндов. например
add bl, ah
имеет задержку 2c от входа BL до выхода BL, поэтому он может добавить задержку к критическому пути, даже если RAX и AH не являются его частью. (Я видел такой вид дополнительной задержки для другого операнда ранее, с векторной задержкой на Skylake, где задержка int/float "загрязняет" регистр навсегда. TODO: запишите это.)
Это означает, что распаковка байтов с помощью movzx ecx, al
/movzx edx, ah
имеет дополнительную задержку по сравнению с movzx
/shr eax,8
/movzx
, но все же повышает пропускную способность.
Чтение AH, когда оно грязное, не добавляет задержки. (
add ah,ah
илиadd ah,dh
/add dh,ah
имеют задержку 1с на добавку). Я не проводил много испытаний, чтобы подтвердить это во многих angular случаях.Гипотеза: грязное значение high8 хранится в нижней части физического регистра. Чтение чистого старшего 8 требует сдвига для извлечения битов [15: 8], но чтение грязного старшего 8 может просто взять биты [7: 0] физического регистра, как при обычном считывании 8-битного регистра.
Дополнительная задержка не означает снижение пропускной способности. Эта программа может работать со скоростью 1 iter на 2 такта, даже если все инструкции add
имеют задержку 2c (при чтении DH, который не изменяется).
global _start
_start:
mov ebp, 100000000
.loop:
add ah, dh
add bh, dh
add ch, dh
add al, dh
add bl, dh
add cl, dh
add dl, dh
dec ebp
jnz .loop
xor edi,edi
mov eax,231 ; __NR_exit_group from /usr/include/asm/unistd_64.h
syscall ; sys_exit_group(0)
Performance counter stats for './testloop':
48.943652 task-clock (msec) # 0.997 CPUs utilized
1 context-switches # 0.020 K/sec
0 cpu-migrations # 0.000 K/sec
3 page-faults # 0.061 K/sec
200,314,806 cycles # 4.093 GHz
100,024,930 branches # 2043.675 M/sec
900,136,527 instructions # 4.49 insn per cycle
800,219,617 uops_issued_any # 16349.814 M/sec
800,219,014 uops_executed_thread # 16349.802 M/sec
1,903 lsd_uops # 0.039 M/sec
0.049107358 seconds time elapsed
Некоторые интересные тестовые циклы:
%if 1
imul eax,eax
mov dh, al
inc dh
inc dh
inc dh
; add al, dl
mov cl,dl
movzx eax,cl
%endif
Runs at ~2.35c per iteration on both HSW and SKL. reading 'dl' has no dep on the 'inc dh' result. But using 'movzx eax, dl' instead of 'mov cl,dl' / 'movzx eax,cl' causes a partial-register merge, and creates a loop-carried dep chain. (8c per iteration).
%if 1
imul eax, eax
imul eax, eax
imul eax, eax
imul eax, eax
imul eax, eax ; off the critical path unless there a false dep
%if 1
test ebx, ebx ; independent of the imul results
;mov ah, 123 ; dependent on RAX
;mov eax,0 ; breaks the RAX dependency
setz ah ; dependent on RAX
%else
mov ah, bl ; dep-breaking
%endif
add ah, ah
;; ;inc eax
; sbb eax,eax
rcr ebx, 1 ; dep on add ah,ah via CF
mov eax,ebx ; clear AH-dirty
;; mov [rdi], ah
;; movzx eax, byte [rdi] ; clear AH-dirty, and remove dep on old value of RAX
;; add ebx, eax ; make the dep chain through AH loop-carried
%endif
Версия setcc (с %if 1
) имеет задержку, переносимую циклом 20c, и запускается из LSD, даже если она имеет setcc ah
и add ah,ah
.
00000000004000e0 <_start.loop>:
4000e0: 0f af c0 imul eax,eax
4000e3: 0f af c0 imul eax,eax
4000e6: 0f af c0 imul eax,eax
4000e9: 0f af c0 imul eax,eax
4000ec: 0f af c0 imul eax,eax
4000ef: 85 db test ebx,ebx
4000f1: 0f 94 d4 sete ah
4000f4: 00 e4 add ah,ah
4000f6: d1 db rcr ebx,1
4000f8: 89 d8 mov eax,ebx
4000fa: ff cd dec ebp
4000fc: 75 e2 jne 4000e0 <_start.loop>
Performance counter stats for './testloop' (4 runs):
4565.851575 task-clock (msec) # 1.000 CPUs utilized ( +- 0.08% )
4 context-switches # 0.001 K/sec ( +- 5.88% )
0 cpu-migrations # 0.000 K/sec
3 page-faults # 0.001 K/sec
20,007,739,240 cycles # 4.382 GHz ( +- 0.00% )
1,001,181,788 branches # 219.276 M/sec ( +- 0.00% )
12,006,455,028 instructions # 0.60 insn per cycle ( +- 0.00% )
13,009,415,501 uops_issued_any # 2849.286 M/sec ( +- 0.00% )
12,009,592,328 uops_executed_thread # 2630.307 M/sec ( +- 0.00% )
13,055,852,774 lsd_uops # 2859.456 M/sec ( +- 0.29% )
4.565914158 seconds time elapsed ( +- 0.08% )
Необъяснимо: он запускается из ЛСД, хотя и делает АХ грязным. (По крайней мере, я так думаю. TODO: попробуйте добавить некоторые инструкции, которые делают что-то с eax
до того, как mov eax,ebx
очистит его.)
Но с mov ah, bl
он работает в 5.0c на итерацию (узкое место пропускной способности imul
) на обоих HSW/SKL. (Закомментированное сохранение/перезагрузка тоже работает, но SKL имеет более быструю пересылку хранилищ, чем HSW, и это с переменной задержкой...)
# mov ah, bl version
5,009,785,393 cycles # 4.289 GHz ( +- 0.08% )
1,000,315,930 branches # 856.373 M/sec ( +- 0.00% )
11,001,728,338 instructions # 2.20 insn per cycle ( +- 0.00% )
12,003,003,708 uops_issued_any # 10275.807 M/sec ( +- 0.00% )
11,002,974,066 uops_executed_thread # 9419.678 M/sec ( +- 0.00% )
1,806 lsd_uops # 0.002 M/sec ( +- 3.88% )
1.168238322 seconds time elapsed ( +- 0.33% )
Обратите внимание, что он больше не запускается из ЛСД.