Режимы микросинтеза и адресации

Я обнаружил что-то неожиданное (для меня) с помощью анализатора кода архитектуры Intel® (IACA).

Следующая инструкция с использованием [base+index] адресация

addps xmm1, xmmword ptr [rsi+rax*1]

не микроплавкий согласно IACA. Однако, если я использую [base+offset] как это

addps xmm1, xmmword ptr [rsi]

МАКА сообщает, что это действительно взрыватель.

В разделе 2-11 справочного руководства по оптимизации Intel в качестве примера приводится следующее "микроплавких микроопераций, которые могут обрабатываться всеми декодерами"

FADD DOUBLE PTR [RDI + RSI*8]

и руководство по сборке оптимизация Agner Fog также дает примеры микрооперации с использованием [base+index] адресации. См., Например, Раздел 12.2 "Тот же пример на Core2". Так какой правильный ответ?

4 ответа

Решение

В декодерах и uop-кеше режим адресации не влияет на микрослияние (за исключением того, что инструкция с непосредственным операндом не может микрозонить режим адресации, относящийся к RIP).

Но некоторые комбинации режима uop и адресации не могут оставаться микроплавкими в ROB (в неработающем ядре), поэтому процессоры семейства Intel SnB "разглаживают" при необходимости, в какой-то момент перед проблемой / переименовать этап. Что касается пропускной способности и размера окна не по порядку (размер ROB), то имеет значение число uop в слитых доменах после расслоения.

Руководство по оптимизации Intel описывает отсутствие ламинирования для Sandybridge в разделе 2.3.2.4: Очередь микроопераций и детектор Loop Stream Detector (LSD), но не описывает изменения для более поздних микроархитектур.


Правила, насколько я могу судить по экспериментам на SnB, HSW и SKL:

  • SnB (и я полагаю, также IvB): индексированные режимы адресации всегда не ламинированы, другие остаются в микроплавком виде. IACA (в основном?) Правильно.
  • HSW, SKL: они сохраняют микрозонку с индексированной инструкцией ALU только в том случае, если она имеет 2 операнда и обрабатывает регистр dst как чтение-изменение-запись. Здесь "операнды" включают флаги, означающие, что adc а также cmov не микроплавкий Большинство VEX-кодированных инструкций также не сливаются, так как они обычно имеют три операнда (поэтому paddb xmm0, [rdi+rbx] но предохранители vpaddb xmm0, xmm0, [rdi+rbx] не делает). Наконец, случайная инструкция с 2 операндами, где первый операнд только для записи, такой как pabsb xmm0, [rax + rbx] также не предохранитель. IACA не прав, применяя правила SnB.

Связанный: простые (неиндексированные) режимы адресации - единственные, которые может обрабатывать выделенный модуль адреса магазина на порту 7 (Haswell и более поздние версии), поэтому все еще потенциально полезно избегать индексированных режимов адресации для хранилищ. (Хороший трюк для этого - обратиться к dst с помощью одного регистра, но src с dst+(initial_src-initial_dst), Тогда вам нужно только увеличить регистр dst внутри цикла.)

Обратите внимание, что некоторые инструкции вообще никогда не сливаются (даже в декодерах / uop-cache). например shufps xmm, [mem], imm8, или же vinsertf128 ymm, ymm, [mem], imm8, всегда 2 мопа на SnB через Skylake, даже если их версии в регистре-источнике только 1 моп. Это типично для команд с операндом управления imm8 плюс обычные операнды dest/src1, регистр / память src2, но есть несколько других случаев. например PSRLW/D/Q xmm,[mem] (векторное смещение из операнда памяти) не имеет микросреды и не имеет значения PMULLD.

См. Также этот пост в блоге Агнера Фога для обсуждения ограничений пропускной способности для HSW/SKL при чтении большого количества регистров: большое количество микросинтезов с индексированными режимами адресации может привести к замедлению по сравнению с теми же инструкциями с меньшим количеством операндов регистров: зарегистрировать режимы адресации и сразу. Мы пока не знаем причину, но я подозреваю, что какое-то ограничение на чтение регистров, возможно, связано с чтением большого количества холодных регистров из PRF.


Контрольные примеры, цифры из реальных измерений: все эти микроплавкие предохранители в декодерах 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's 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.

Я предполагаю, что Broadwell ведет себя как Skylake для ADC / CMOV.

Странно, что HSW не ламинирует источник памяти ADC и CMOV. Возможно, Intel не смогла изменить это с SnB до того, как они уложились в срок поставки Haswell.

В таблице Агнера говорится: cmovcc r,m а также adc r,m на HSW/SKL вообще не сливаюсь, но это не соответствует моим экспериментам. Количество циклов, которое я измеряю, совпадает с числом проблем с мопами в слитых доменах, для узкого места проблемы 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,

Память-назначения adc так много мопов, потому что семейство Intel P6 (и, по-видимому, семейство SnB) не может хранить одинаковые записи TLB для всех мопов многопользовательской инструкции, поэтому для обхода проблемного случая, когда загрузка и добавление завершены, а затем происходит сбой хранилища, но insn нельзя просто перезапустить, потому что CF уже обновлен. Интересная серия комментариев от Энди Глеу (@krazyglew).

Предположительно, слияние в декодерах и отсутствие ламинирования в дальнейшем избавляет нас от необходимости использования микрокода ПЗУ для производства более 4-х мопов с плавкой областью из одной инструкции для adc [base+idx], reg,


Почему нон-ламинаты семейства SnB:

Sandybridge упростил внутренний формат UOP для экономии энергии и транзисторов (наряду с внесением основных изменений в использование файла физического регистра вместо сохранения данных ввода / вывода в ROB). Процессоры семейства SnB допускают только ограниченное количество входных регистров для uop слитых доменов в ядре с неправильным порядком. Для SnB/IvB этот предел составляет 2 входа (включая флаги). Для HSW и позже, предел составляет 3 входа для моп. Я не уверен, если память назначения add а также adc в полной мере воспользоваться этим, или если Intel пришлось вывести Haswell за дверь с некоторыми инструкциями

Nehalem и более ранние версии имеют ограничение в 2 входа для мопов с неиспользуемым доменом, но ROB, по-видимому, может отслеживать микроплавкие мопы с 3 входными регистрами (операнд регистра без памяти, база и индекс).


Таким образом, индексированные хранилища и инструкции загрузки ALU+ могут все еще эффективно декодироваться (не обязательно быть первым мопом в группе) и не занимать дополнительное место в кеше мопов, но в противном случае преимущества микро-слияния по существу уходят на настройку плотные петли. "un-lamination" происходит до того, как ядро ​​4-fused-domain-uops-per-loop выдает / выводит из строя ширину ядра. Счетчики производительности слитого домена (uops_issued / uops_retired.retire_slots) подсчитывают количество мопов слитого домена после отсутствия ламинирования.

В описании Intel по переименованию (Раздел 2.3.3.1: Renamer) подразумевается, что именно этап выпуска / переименования фактически выполняет неслоение, так что мопы, предназначенные для несламинирования, могут все еще быть микроплавлеными в сплавленном 28/56/64 -domain uop выдает очередь / loop-buffer (также известный как IDQ).

ТОДО: проверить это. Создайте цикл, который едва помещается в буфер цикла. Измените что-нибудь так, чтобы один из мопов был не ламинирован перед выдачей, и посмотрите, работает ли он из буфера цикла (LSD), или же все мопы теперь повторно выбираются из кеша мопов (DSB). Есть счетчики перфокарт, чтобы отследить, откуда происходят мопы, так что это должно быть легко.

Сложнее TODO: если происходит не ламинирование между чтением из кэша UOP и добавлением в IDQ, проверьте, может ли оно когда-либо уменьшить пропускную способность кэша UOP. Или, если расслоение происходит прямо на этапе выпуска, может ли это повлиять на пропускную способность выпуска? (т.е. как он обрабатывает оставшиеся мопы после выдачи первых 4.)


(См. Предыдущую версию этого ответа для некоторых предположений, основанных на настройке некоторого кода LUT, с некоторыми примечаниями по vpgatherdd примерно в 1,7 раза больше циклов, чем 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, который больше не работает.

Confirmed by testing with performance counters for uops and cycles.

I found a table of PMU events for Intel Sandybridge, for use with Linux's perf команда. (Standard perf unfortunately doesn't have symbolic names for most hardware-specific PMU events, like uops.) I made use of it for a recent answer.

ocperf.py provides symbolic names for these uarch-specific PMU events, so you don't have to look up tables. Also, the same symbolic name works across multiple uarches. I wasn't aware of it when I first wrote this answer.

To test for uop micro-fusion, I constructed a test program that is bottlenecked on the 4-uops-per-cycle fused-domain limit of Intel CPUs. To avoid any execution-port contention, many of these uops are nop s, which still sit in the uop cache and go through the pipeline the same as any other uop, except they don't get dispatched to an execution port. (An xor x, same, or an eliminated move, would be the 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

I also found that the uop bandwidth out of the loop buffer isn't a constant 4 per cycle, if the loop isn't a multiple of 4 uops. (ie it's abc, abc,...; не abca, bcab...) Agner Fog's microarch doc unfortunately wasn't clear on this limitation of the loop buffer. See /questions/47775236/snizhaetsya-li-proizvoditelnost-pri-vyipolnenii-tsiklov-chislo-operatsij-kotoryih-ne-kratno-shirine-protsessora for more investigation on HSW/SKL. SnB may be worse than HSW in this case, but I'm not sure and don't still have working SnB hardware.

I wanted to keep macro-fusion (compare-and-branch) out of the picture, so I used nop между dec and the branch. I used 4 nop s, so with micro-fusion, the loop would be 8 uops, and fill the pipeline with at 2 cycles per 1 iteration.

In the other version of the loop, using 2-operand addressing modes that don't micro-fuse, the loop will be 10 fused-domain uops, and run in 3 cycles.

Results from my 3.3GHz Intel Sandybridge (i5 2500k). I didn't do anything to get the cpufreq governor to ramp up clock speed before testing, because cycles are cycles when you aren't interacting with memory. I've added annotations for the performance counter events that I had to enter in hex.

testing the 1-reg addressing mode: 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

testing the 2-reg addressing mode: with a 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

So, both versions ran 80M instructions, and dispatched 60M uops to execution ports. (or with a memory source dispatches to an ALU for the or, and a load port for the load, regardless of whether it was micro-fused or not in the rest of the pipeline. nop doesn't dispatch to an execution port at all.) Similarly, both versions retire 100M unfused-domain uops, because the 40M nops count here.

The difference is in the counters for the fused-domain.

  1. The 1-register address version only issues and retires 80M fused-domain uops. This is the same as the number of instructions. Each insn turns into one fused-domain uop.
  2. The 2-register address version issues 100M fused-domain uops. This is the same as the number of unfused-domain uops, indicating that no micro-fusion happened.

I suspect that you'd only see a difference between UOPS_ISSUED and UOPS_RETIRED(retirement slots used) if branch mispredicts led to uops being cancelled after issue, but before retirement.

And finally, the performance impact is real. The non-fused version took 1.5x as many clock cycles. This exaggerates the performance difference compared to most real cases. The loop has to run in a whole number of cycles, and the 2 extra uops push it from 2 to 3. Often, an extra 2 fused-domain uops will make less difference. And potentially no difference, if the code is bottlecked by something other than 4-fused-domain-uops-per-cycle.

Still, code that makes a lot of memory references in a loop might be faster if implemented with a moderate amount of unrolling and incrementing multiple pointers which are used with simple [base + immediate offset] addressing, instead of the using [base + index] addressing modes.

futher stuff

RIP-relative with an immediate can't micro-fuse. Agner Fog's testing shows that this is the case even in the decoders / uop-cache, so they never fuse in the first place (rather than being un-laminated).

IACA gets this wrong, and claims that both of these micro-fuse:

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's testing
cmp dword  [rel mydata], 0x1b   ; fused counters ~= unfused counters (micro-fusion didn't happen)

RIP-rel does micro-fuse (and stay fused) when there's no immediate, eg:

or  eax, dword  [rel mydata]    ; fused counters != unfused counters, i.e. micro-fusion happens

Micro-fusion doesn't increase the latency of an instruction. The load can issue before the other input is ready.

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

This loop runs at 5 cycles per iteration, because of the eax dep chain. No faster than a sequence of or eax, [rsi + 0 + rdi], или же mov ebx, [rsi + 0 + rdi] / or eax, ebx, (The unfused and the mov versions both run the same number of uops.) Scheduling / dep checking happens in the unfused-domain. Newly issued uops go into the scheduler (aka Reservation Station (RS)) as well as the ROB. They leave the scheduler after dispatching (aka being sent to an execution unit), but stay in the ROB until retirement. So the out-of-order window for hiding load latency is at least the scheduler size ( 54 unfused-domain uops in Sandybridge, 60 in Haswell, 97 in Skylake).

Micro-fusion doesn't have a shortcut for the base and offset being the same register. A loop with or eax, [mydata + rdi+4*rdi] (where rdi is zeroed) runs as many uops and cycles as the loop with or eax, [rsi+rdi], This addressing mode could be used for iterating over an array of odd-sized structs starting at a fixed address. This is probably never used in most programs, so it's no surprise that Intel didn't spend transistors on allowing this special-case of 2-register modes to micro-fuse. (And Intel documents it as "indexed addressing modes" anyway, where a register and scale factor are needed.)


Macro-fusion of a cmp / jcc или же dec / jcc creates a uop that stays as a single uop even in the unfused-domain. dec / nop / jge can still run in a single cycle but is three uops instead of one.

Примечание: с тех пор как я написал этот ответ, Питер также проверил Haswell и Skylake и интегрировал результаты в принятый выше ответ (в частности, большинство улучшений, которые я приписываю Skylake ниже, похоже, действительно появилось в Haswell). Вы должны увидеть этот ответ для краткого описания поведения разных процессоров, и этот ответ (хотя и не неправильный) в основном представляет исторический интерес.

Мои тесты показывают, что на Skylake по крайней мере 1 процессор полностью объединяет даже сложные режимы адресации, в отличие от Sandybridge.

То есть версии кода с 1 и 2 аргументами, опубликованные Питером выше, выполняются с одинаковым числом циклов, с одинаковым числом отправленных и удаленных мопов.

Мои результаты:

Статистика счетчика производительности для ./uop-test:

     23.718772      task-clock (msec)         #    0.973 CPUs utilized          
    20,642,233      cycles                    #    0.870 GHz                    
    80,111,957      instructions              #    3.88  insns per cycle        
    60,253,831      uops_executed_thread      # 2540.344 M/sec                  
    80,295,685      uops_issued_any           # 3385.322 M/sec                  
    80,176,940      uops_retired_retire_slots # 3380.316 M/sec                  

   0.024376698 seconds time elapsed

Статистика счетчика производительности для ./uop-test x:

     13.532440      task-clock (msec)         #    0.967 CPUs utilized          
    21,592,044      cycles                    #    1.596 GHz                    
    80,073,676      instructions              #    3.71  insns per cycle        
    60,144,749      uops_executed_thread      # 4444.487 M/sec                  
    80,162,360      uops_issued_any           # 5923.718 M/sec                  
    80,104,978      uops_retired_retire_slots # 5919.478 M/sec                  

   0.013997088 seconds time elapsed

Статистика счетчика производительности для ./uop-test x x:

     16.672198      task-clock (msec)         #    0.981 CPUs utilized          
    27,056,453      cycles                    #    1.623 GHz                    
    80,083,140      instructions              #    2.96  insns per cycle        
    60,164,049      uops_executed_thread      # 3608.645 M/sec                  
   100,187,390      uops_issued_any           # 6009.249 M/sec                  
   100,118,409      uops_retired_retire_slots # 6005.112 M/sec                  

   0.016997874 seconds time elapsed

Я не нашел ни одной инструкции UOPS_RETIRED_ANY на Skylake, только парень с "удаленными слотами", который, по-видимому, является слитым доменом.

Финальный тест (uop-test x x) вариант, который предлагает Питер, который использует RIP-родственник cmp с немедленной, которая, как известно, не микрофузить:

.loop_riprel
    cmp dword [rel mydata], 1
    cmp dword [rel mydata], 2
    dec ecx
    nop
    nop
    nop
    nop
    jg .loop_riprel

Результаты показывают, что дополнительные 2 мопа за цикл регистрируются счетчиками, выпущенными и выбывшими мопами (следовательно, тест может различать происходящее слияние, а не).

Дополнительные тесты на других архитектурах приветствуются! Вы можете найти код (скопированный из Питера выше) в github.


[1]... и, возможно, некоторые другие архитектуры между Skylake и Sandybridge, так как Питер только проверял SB, а я только SKL.

Старые процессоры Intel без UOP-кеша могут сделать слияние, так что, возможно, это недостаток UOP-кеша. У меня нет времени, чтобы проверить это прямо сейчас, но я добавлю тест на UOP Fusion в следующий раз, когда обновлю свои тестовые сценарии. Вы пробовали с инструкциями FMA? Это единственные инструкции, которые допускают 3 входных зависимости в неиспользованном мопе.

Теперь я рассмотрел результаты испытаний для Intel Sandy Bridge, Ivy Bridge, Haswell и Broadwell. У меня еще не было доступа к тестированию на Skylake. Результаты:

  • Инструкции с двумя регистрами адресации и тремя входными зависимостями смешиваются. Они занимают только одну запись в кэше микроопераций, если они содержат не более 32 бит данных (или 2 * 16 бит).
  • Можно создать инструкции с четырьмя входными зависимостями, используя объединенные инструкции умножения и добавления в Haswell и Broadwell. Эти инструкции все еще объединяются в одну микрооперацию и занимают только одну запись в кэше микрооперации.
  • Инструкции с более чем 32-битными данными, например, 32-битный адрес и 8-битные непосредственные данные могут все еще сливаться, но использовать две записи в кеше микрооперации (если 32-битные не могут быть сжаты в 16-битное целое число со знаком)
  • Инструкции с относительной адресацией rip и непосредственной константой не смешиваются, даже если смещение и непосредственная константа очень малы.
  • Все результаты идентичны на четырех протестированных машинах.
  • Тесты проводились с моими собственными тестовыми программами с использованием счетчиков контроля производительности в циклах, которые были достаточно малы, чтобы поместиться в кэш микроопераций.

Ваши результаты могут быть связаны с другими факторами. Я не пытался использовать IACA.

Другие вопросы по тегам