@BeeOnRope: они всегда имеют непосредственную ширину с регистром (кроме rax). Вот почему последние правки об этом говорят «al / ax / eax / rax, imm8 / 16/32/32». Может быть, я должен добавить «соответственно» к этим уже загроможденным предложениям.
Haswell и более ранних версиях обычно составляет 2 мопа с задержкой в 2 цикла, поскольку у мопов Intel традиционно может быть только 2 входа (https://agner.org/optimize/). Broadwell / Skylake и более поздние имеют ADC / SBB / CMOV с одним мопом, после того как Haswell представил мопы с 3 входами для FMA имикросинтез индексированных режимов адресации в некоторых случаях.
(Но не дляadc al, imm8
краткая кодировка или другие краткие формы al / ax / eax / rax, imm8 / 16/32/32 без ModRM. Более подробно в моем ответе.)
Ноadc
с немедленным 0 - это специальный случай в Haswell для декодирования как единственного мопа. @BeeOnRope проверил этои включил проверку для этогопричуда производительности в его урочище:https://github.com/travisdowns/uarch-bench, Пример вывода изCI на сервере Haswell показывая разницу междуadc reg,0
а такжеadc reg,1
или жеadc reg,zeroed-reg
.
(То же самое для SBB. Насколько я видел, никогда не будет никакой разницы между производительностью АЦП и SBB для эквивалентного кодирования с одинаковыми непосредственными значениями на любом ЦП.)
Когда была эта оптимизация дляimm=0
представил?
$22https://agner.org/optimize/23$adc eax,0
задержка составляет 2 цикла, так же, какadc eax,3
, А также количество циклов идентично для нескольких вариантов тестов пропускной способности с0
против3
поэтому Core 2 первого поколения (Conroe / Merom) не выполняет эту оптимизацию.
Самый простой способ ответить на это, вероятно, использовать мою тестовую программу ниже в системе Sandybridge, и посмотреть, еслиadc eax,0
быстрее чемadc eax,1
, Но ответы, основанные на надежной документации, тоже подойдут.
(Кстати, если у кого-то есть доступ к счетчикам производительности на Сэндибридже, вы также можете разгадать тайнуСнижается ли производительность при выполнении циклов, число операций которых не кратно ширине процессора? запустив тестовый код @ BeeOnRope. Или производительность, которую я наблюдал на моем неработающем SnB, была просто результатом отсутствия ламинирования, отличного от нормального мопса?)
Сноска 1Я использовал эту тестовую программу на моем Core 2 E6600 (Conroe / Merom), работающем под Linux.
;; NASM / YASM
;; assemble / link this into a 32 or 64-bit static executable.
global _start
_start:
mov ebp, 100000000
align 32
.loop:
xor ebx,ebx ; avoid partial-flag stall but don't break the eax dependency
%rep 5
adc eax, 0 ; should decode in a 2+1+1+1 pattern
add eax, 0
add eax, 0
add eax, 0
%endrep
dec ebp ; I could have just used SUB here to avoid a partial-flag stall
jg .loop
%ifidn __OUTPUT_FORMAT__, elf32
;; 32-bit sys_exit would work in 64-bit executables on most systems, but not all. Some, notably Window's subsystem for Linux, disable IA32 compat
mov eax,1
xor ebx,ebx
int 0x80 ; sys_exit(0) 32-bit ABI
%else
xor edi,edi
mov eax,231 ; __NR_exit_group from /usr/include/asm/unistd_64.h
syscall ; sys_exit_group(0)
%endif
Linuxperf
не очень хорошо работает на старых процессорах, таких как Core 2 (он не знает, как получить доступ ко всем событиям, таким как uops), но он знает, как читать счетчики HW для циклов и инструкций. Этого достаточно.
Я построил и профилировал это с
yasm -felf64 -gdwarf2 testloop.asm
ld -o testloop-adc+3xadd-eax,imm=0 testloop.o
# optional: taskset pins it to core 1 to avoid CPU migrations
taskset -c 1 perf stat -e task-clock,context-switches,cycles,instructions ./testloop-adc+3xadd-eax,imm=0
Performance counter stats for './testloop-adc+3xadd-eax,imm=0':
1061.697759 task-clock (msec) # 0.992 CPUs utilized
100 context-switches # 0.094 K/sec
2,545,252,377 cycles # 2.397 GHz
2,301,845,298 instructions # 0.90 insns per cycle
1.069743469 seconds time elapsed
0,9 IPC интересный номер здесь.
Это примерно то, что мы ожидаем от статического анализа с задержкой 2 моп / 2 сadc
: (5*(1+3) + 3) = 23
инструкции в цикле,5*(2+3) = 25
циклы задержки = циклы за цикл итерации. 23/25 = 0,92.
На Скайлэйке 1.15.(5*(1+3) + 3) / (5*(1+3)) = 1.15
, то есть дополнительные 0,15 от xor-zero и dec / jg, в то время как цепочка adc / add работает ровно 1 моп / такт, узкое место по задержке. Мы ожидаем, что этот IPC будет равен 1.15 для любого другого uarch с задержкой одного циклаadc
Кроме того, потому что интерфейс не является узким местом. (In-order Atom и P5 Pentium будут немного ниже, но xor и dec могут соединиться с adc или добавить на P5.)
По скл,uops_issued.any
= instructions
= 2.303G, подтверждая, чтоadc
является единственным UOP (который всегда находится на SKL, независимо от того, какое значение имеет непосредственный). Случайно,jg
это первая инструкция в новой строке кэша, поэтому она не сливается с макросомdec
на скл. С участиемdec rbp
или жеsub ebp,1
вместо,uops_issued.any
это ожидаемый 2.2G.
Это чрезвычайно повторяется:perf stat -r5
(запустить его 5 раз и показать среднее значение + дисперсию), и несколько прогонов показали, что счетчик циклов можно повторить до 1 части на 1000. 1с против задержки 2с вadc
сделал бымного большая разница, чем это.
Восстановление исполняемого файла с немедленным, кроме0
не меняет временивообще на Core 2, еще один сильный признак того, что нет особого случая. Это определенно стоит проверить.
Я изначально смотрел на пропускную способность (сxor eax,eax
перед каждой итерацией цикла, позволяя OoO выполнять перекрывающиеся итерации), но было трудно исключить внешние эффекты. Я думаю наконецсделал Избегайте узких мест переднего плана, добавив Single UOPadd
инструкции. Тестирование пропускной способности внутреннего цикла выглядит следующим образом:
xor eax,eax ; break the eax and CF dependency
%rep 5
adc eax, 0 ; should decode in a 2+1+1+1 pattern
add ebx, 0
add ecx, 0
add edx, 0
%endrep
Вот почему версия с латентным тестом выглядит довольно странно. Но, в любом случае, помните, что Core2 не имеет кеша декодированного uop, и его буфер цикла находится на стадии предварительного декодирования (после нахождения границ команд). Только 1 из 4 декодеров может декодировать многопользовательские инструкции, поэтомуadc
будучи многопользовательскими узкими местами на переднем конце. Я думаю, я мог бы просто позволить этому случиться сtimes 5 adc eax, 0
, поскольку маловероятно, что какой-то более поздний этап конвейера сможет выбросить этот моп, не выполняя его.
Циклический буфер Nehalem перерабатывает декодированные мопы и позволит избежать этого узкого места декодирования для многопользовательских инструкций.