@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 перерабатывает декодированные мопы и позволит избежать этого узкого места декодирования для многопользовательских инструкций.

Ответы на вопрос(2)

Ваш ответ на вопрос