Modos de micro fusão e endereçamento

Eu encontrei algo inesperado (para mim) usando o Intel® Architecture Code Analyzer (IACA).

As seguintes instruções usando o endereçamento [base+index]

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

não fusiona de acordo com o IACA. No entanto, se eu usar [base+offset] como este

 addps xmm1, xmmword ptr [rsi] 

A IACA relata que se funde.

A seção 2-11 do manual de referência de otimização da Intel fornece o seguinte como um exemplo de “micro-operações micro fusíveis que podem ser manipuladas por todos os decodificadores”.

 FADD DOUBLE PTR [RDI + RSI*8] 

e o manual de assembly de otimização da Agner Fog também fornece exemplos de fusão micro-op usando endereçamento [base+index] . Veja, por exemplo, Seção 12.2 “O mesmo exemplo no Core2”. Então, qual é a resposta correta?

Nos decodificadores e no cache uop, o modo de endereçamento não afeta a microfusão (exceto que uma instrução com um operando imediato não pode micro-fundir um modo de endereçamento relativo ao RIP).

Mas algumas combinações de uop e modo de endereçamento não podem permanecer micro-fundidas no ROB (no núcleo fora de ordem), então as CPUs da família Intel SnB “não laminam” quando necessário, em algum momento antes do problema / renomeie o estágio. Para a taxa de transferência de problemas e o tamanho da janela fora de ordem (tamanho ROB), a contagem de uop do domínio fundido após a não laminação é o que interessa.

O manual de otimização da Intel descreve a não-laminação para Sandybridge na Seção 2.3.2.4: Fila de Micro-op e Detector de Fluxo de Circuito (LSD) , mas não descreve as alterações para quaisquer microarquiteturas posteriores.


As regras , da melhor maneira que posso perceber em experimentos com SnB, HSW e SKL:

  • SnB (e eu assumo também IvB): os modos de endereçamento indexados são sempre não-laminados, outros permanecem micro-fundidos. A IACA é (principalmente?) Correta.
  • HSW, SKL: Estes mantêm apenas uma instrução ALU indexada microfundada se ela tiver 2 operandos e tratar o registrador dst como read-modify-write. Aqui, operandos incluem flags, o que significa que adc e cmov não se fundem. A maioria das instruções codificadas em VEX também não se fundem, pois geralmente têm três operandos (então paddb xmm0, [rdi+rbx] funde mas vpaddb xmm0, xmm0, [rdi+rbx] não). Finalmente, a instrução ocasional de 2 operandos onde o primeiro operando é somente gravação, como pabsb xmm0, [rax + rbx] também não se funde. A IACA está errada, aplicando as regras do SnB.

Relacionados: os modos de endereçamento simples (não indexados) são os únicos que a unidade de endereço de armazenamento dedicada na porta 7 (Haswell e posterior) pode manipular, portanto, ainda é potencialmente útil evitar modos de endereçamento indexados para armazenamentos. (Um bom truque para isso é endereçar seu dst com um único registro, mas src com dst+(initial_src-initial_dst) . Então você só precisa incrementar o registro dst dentro de um loop).

Note que algumas instruções nunca se fundem em nada (mesmo nos decoders / uop-cache). Por exemplo, shufps xmm, [mem], imm8 ou vinsertf128 ymm, ymm, [mem], imm8 , são sempre 2 uops em SnB através do Skylake, mesmo que suas versões de fonte de registro sejam apenas 1 uop. Isso é típico para instruções com um operando de controle imm8 mais os operandos de registro / memory sr / src1, src2, mas existem alguns outros casos. por exemplo, PSRLW/D/Q xmm,[mem] (contagem de deslocamento de vetor de um operando de memory) não micro-fusível, e nem PMULLD.

Veja também este post no blog da Agner Fog para discussão sobre limites de throughput de problemas em HSW / SKL quando você lê muitos registradores: Lotes de microfusão com modos de endereçamento indexados podem levar a lentidão versus as mesmas instruções com menos operandos de registro: um registre modos de endereçamento e imediatos. Ainda não sabemos a causa, mas suspeito de algum tipo de limite de leitura de registro, talvez relacionado à leitura de muitos registros frios do PRF.


Casos de teste, números de medições reais : Todos esses micro-fusíveis nos decodificadores, AFAIK, mesmo que sejam posteriormente não laminados.

 # 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 

Instruções de três inputs que a HSW / SKL pode ter para não laminar

 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. 

Eu suponho que Broadwell se comporte como Skylake para adc / cmov.

É estranho que o HSW cancele o ADC e o CMOV da fonte de memory. Talvez a Intel não tenha conseguido mudar isso da SnB antes de chegar ao prazo final para o envio de Haswell.

A tabela de cmovcc r,m da Agner diz ” cmovcc r,m e adc r,m eu não micro fusível em tudo em HSW / SKL, mas isso não corresponde aos meus experimentos. As contagens de ciclo estão medindo a correspondência com a contagem de problemas do uop do domínio fundido, para um gargalo de problema de 4 uops / clock. Espero que ele verifique novamente e corrija as tabelas.

Inteiro de dest-memory 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.) 

Sim, é verdade, adc [rdi],eax / dec ecx / jnz é executado mais rápido do que o mesmo loop com add vez de adc no SKL. Eu não tentei usar endereços diferentes, já que claramente a SKL não gosta de reescritas repetidas do mesmo endereço (latência de encaminhamento de loja maior do que o esperado. Veja também este post sobre armazenamento / recarregamento repetido para o mesmo endereço sendo mais lento do que o esperado na SKL .

O destino da memory adc é tão grande porque a família Intel P6 (e aparentemente a família SnB) não pode manter as mesmas inputs de TLB para todas as instruções de uma instrução multi-uop, então é necessário um trabalho extra para contornar o problema -case onde a carga e adicionar completa e, em seguida, a loja falhas, mas o insn não pode ser reiniciado porque CF já foi atualizado . Interessante série de comentários de Andy Glew (@ krazyglew).

Presumivelmente, a fusão nos decodificadores e na não-laminação posteriormente nos poupa da necessidade de ROM de microcódigo para produzir mais de 4 uops de domínio fundido a partir de uma única instrução para adc [base+idx], reg .


Por que a família SnB não lamina :

A Sandybridge simplificou o formato uop interno para economizar energia e transistores (além de fazer a grande mudança no uso de um arquivo de registro físico, em vez de manter os dados de input / saída no ROB). As CPUs da família SnB permitem apenas um número limitado de registros de input para um domínio fundido no núcleo fora de ordem. Para SnB / IvB, esse limite é de 2 inputs (incluindo sinalizadores). Para HSW e posterior, o limite é de 3 inputs para um uop. Eu não tenho certeza se o memory-destination add e adc estão aproveitando ao máximo isso, ou se a Intel teve que levar Haswell para fora da porta com algumas instruções

Nehalem e anteriormente têm um limite de 2 inputs para um domínio não-fundido, mas o ROB aparentemente pode rastrear uops micro-fundidos com 3 registros de input (o operando registrador de não memory, base e índice).


Portanto, os repositorys indexados e as instruções de carga do ALU + ainda podem ser decodificadas com eficiência (não precisando ser o primeiro uop em um grupo) e não ocupam espaço extra no cache do uop, mas, caso contrário, as vantagens da microfusão praticamente desaparecem laços apertados. “un-lamination” ocorre antes do núcleo fora de ordem com largura de emissão / desativação do domínio 4-fundido-uops-por-ciclo . Os contadores de desempenho de domínio fundido (uops_issued / uops_retired.retire_slots) contam os uops de domínio fundido após a não-laminação.

A descrição do renamer pela Intel ( Seção 2.3.3.1: Renamer ) indica que é a etapa de emissão / renomeação que realmente faz a não-laminação, portanto, os uops destinados à não-laminação ainda podem ser micro-fundidos nos 28/56/64 fundidos -domain uop issue queue / loop-buffer (também conhecido como IDQ).

TODO: teste isso. Faça um loop que mal deve caber no buffer de loop. Altere algo para que um dos uops seja desflatinado antes da emissão, e veja se ele ainda é executado a partir do buffer de loop (LSD), ou se todos os uops agora são recuperados do cache uop (DSB). Existem contadores de desempenho para rastrear de onde vêm os uops, então isso deve ser fácil.

Mais difícil TODO: se a não laminação acontece entre a leitura do cache uop e a inclusão no IDQ, teste se ela pode reduzir a largura de banda do cache uop. Ou, se a dessorting acontece no estágio de emissão, isso pode prejudicar a taxa de transferência? (ou seja, como ele manipula as sobras de uops após a emissão do primeiro 4.)


(Veja a versão anterior desta resposta para algumas suposições baseadas no ajuste de algum código LUT, com algumas notas no vpgatherdd sendo aproximadamente 1.7x mais ciclos do que um loop pinsrw .)

Testes experimentais em SnB

Os números de HSW / SKL foram medidos em um i5-4210U e um i7-6700k. Ambos tinham HT habilitado (mas o sistema ocioso para que o thread tivesse todo o núcleo para si mesmo). Eu executei os mesmos binários estáticos em ambos os sistemas, o Linux 4.10 no SKL e o Linux 4.8 no HSW, usando o ocperf.py . (O laptop HSW NFS montou minha área de trabalho SKL / home.)

Os números de SnB foram medidos como descrito abaixo, em um i5-2500k que não está mais funcionando.

Confirmado pelo teste com contadores de desempenho para uops e ciclos.

Eu encontrei uma tabela de events de PMU para Intel Sandybridge , para uso com o comando perf do Linux. (O padrão de perf infelizmente não tem nomes simbólicos para a maioria dos events de PMU específicos de hardware, como uops.) Eu usei-o para uma resposta recente .

ocperf.py fornece nomes simbólicos para esses events de PMU específicos do uarch , portanto, você não precisa procurar por tabelas. Além disso, o mesmo nome simbólico funciona em vários uarches. Eu não estava ciente disso quando escrevi esta resposta pela primeira vez.

Para testar a micro-fusão do uop, construí um programa de teste que é afunilado no limite de 4-uops por ciclo de domínio fundido dos processadores da Intel. Para evitar qualquer contenção de porta de execução, muitos desses uops são nop s, que ainda estão no cache do uop e passam pelo pipeline da mesma forma que qualquer outro uop, exceto que eles não são despachados para uma porta de execução. (Um xor x, same ou um movimento eliminado seria o mesmo.)

Programa de teste: 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 

Eu também descobri que a largura de banda fora do buffer de loop não é uma constante 4 por ciclo, se o loop não for um múltiplo de 4 uops. (isto é, abc , abc , …; não abca , abca , …). O doc de microarca de Agner Fog infelizmente não estava claro sobre essa limitação do buffer de loop. Ver O desempenho é reduzido ao executar loops cuja contagem de retorno não é um múltiplo da largura do processador? para mais investigação sobre HSW / SKL. O SnB pode ser pior do que o HSW neste caso, mas não tenho certeza e ainda não tenho hardware SnB funcionando.

Eu queria manter a macro-fusão (compare-e-branch) fora da imagem, então usei nop s entre o dec e o branch. Eu usei 4 nop , então com micro-fusão, o loop seria 8 uops e preencher o pipeline com 2 ciclos por 1 iteração.

Na outra versão do loop, usando modos de endereçamento de 2 operandos que não são micro-fusíveis, o loop será 10 uops de domínio fundido e executado em 3 ciclos.

Resultados da Intel Sandybridge 3.3GHz (i5 2500k). Eu não fiz nada para fazer com que o cpufreq governor aumentasse a velocidade do clock antes do teste, porque os ciclos são ciclos quando você não está interagindo com a memory. Eu adicionei annotations para os events do contador de desempenho que eu tive que inserir em hexadecimal.

testando o modo de endereçamento 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 

testando o modo de endereçamento 2-reg: com um argumento cmdline

 $ 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 

Portanto, ambas as versões executaram instruções do 80M e despacharam 60M para as portas de execução. ( or com uma fonte de memory despachada para uma ALU para a or e uma porta de carregamento para a carga, independentemente de ter sido microfundada ou não no restante do pipeline. nop não despacha para uma porta de execução em todos os .) Da mesma forma, ambas as versões retiram os uops de domínio não fundido de 100M, porque os 40M não contam aqui.

A diferença está nos contadores do domínio fundido.

  1. A versão do endereço de 1 registro só emite e retira os uops de domínio fundido de 80M. Isso é o mesmo que o número de instruções. Cada insn se transforma em um uop de domínio fundido.
  2. A versão do endereço com 2 registradores emite 100 milhões de domínios fundidos. Este é o mesmo que o número de uops de domínio não fundido, indicando que não houve microfusão.

Eu suspeito que você só veria uma diferença entre UOPS_ISSUED e UOPS_RETIRED (slots de aposentadoria usados) se desvios de agência levassem a cancelamentos após a emissão, mas antes da aposentadoria.

E finalmente, o impacto no desempenho é real. A versão não fundida levou 1.5x como muitos ciclos de clock. Isso exagera a diferença de desempenho em comparação com a maioria dos casos reais. O loop tem que ser executado em um número inteiro de ciclos, e os dois uops extras o empurram de 2 para 3. Muitas vezes, um uops extra de domínio fundido fará menos diferença. E potencialmente nenhuma diferença, se o código é verificado por algo diferente de 4-fundido-domínio-uops-por-ciclo.

Ainda assim, o código que faz muitas referências de memory em um loop pode ser mais rápido se implementado com uma quantidade moderada de desenrolamento e incrementando vários pointers que são usados ​​com endereçamento simples [base + immediate offset] , em vez de usar [base + index] modos de endereçamento.

mais coisas

O RIP-relative com um imediato não pode micro-fusível . Os testes da Agner Fog mostram que este é o caso mesmo nos decoders / uop-cache, então eles nunca se fundem em primeiro lugar (ao invés de serem não laminados).

A IACA entende isso errado e alega que ambos os micro-fusíveis

 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 faz micro-fusível (e fica fundido) quando não há imediato, por exemplo:

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

Micro-fusão não aumenta a latência de uma instrução . A carga pode ser emitida antes que a outra input esteja pronta.

 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 

Esse loop é executado em 5 ciclos por iteração, devido à cadeia dep eax . Não é mais rápido que uma sequência de or eax, [rsi + 0 + rdi] , ou mov ebx, [rsi + 0 + rdi] / or eax, ebx . (O unused e as versões mov ambos executam o mesmo número de uops.) A verificação de agendamento / dep ocorre no domínio não fundido. Os uops recém-emitidos vão para o agendador (também conhecido como Estação de Reserva (RS)), bem como para o ROB. Eles deixam o agendador após o despacho (também conhecido como sendo enviado para uma unidade de execução), mas permanecem no ROB até a aposentadoria. Portanto, a janela fora de ordem para ocultar a latência de carga é pelo menos o tamanho do agendador ( 54 uops de domínio não fundido em Sandybridge, 60 em Haswell , 97 em Skylake).

A micro fusão não tem um atalho para a base e o offset é o mesmo registro. Um loop com or eax, [mydata + rdi+4*rdi] (em que rdi é zerado) é executado como muitos uops e ciclos como o loop com or eax, [rsi+rdi] . Esse modo de endereçamento poderia ser usado para iterar sobre uma matriz de estruturas de tamanhos ímpares iniciando em um endereço fixo. Provavelmente, isso nunca é usado na maioria dos programas, portanto, não é surpresa que a Intel não gaste transistores ao permitir que esse caso especial de dois modos de registro se micro-fundam. (E a Intel documenta isso como “modos de endereçamento indexados” de qualquer maneira, onde um registro e um fator de escala são necessários.)


A macro-fusão de um cmp / jcc ou dec / jcc cria um uop que permanece como um único uop, mesmo no domínio não-fundido. dec / nop / jge ainda pode ser executado em um único ciclo, mas é três uops em vez de um.

Nota: Desde que escrevi esta resposta, Peter também testou Haswell e Skylake e integrou os resultados na resposta aceita acima (em particular, a maioria das melhorias que atribuo ao Skylake abaixo parece ter aparecido em Haswell). Você deve ver essa resposta para o resumo do comportamento entre CPUs e essa resposta (embora não esteja errada) é principalmente de interesse histórico.

Meu teste indica que no Skylake pelo menos 1 , o processador combina totalmente os modos de endereçamento complexos, ao contrário de Sandybridge.

Ou seja, as versões 1-arg e 2-arg do código postado acima por Peter são executadas no mesmo número de ciclos, com o mesmo número de uops despachados e retirados.

Meus resultados:

Estatísticas do contador de desempenho para ./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 

Estatísticas do contador de desempenho para ./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 

Estatísticas do contador de desempenho para ./uop-test xx :

  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 

Eu não encontrei nenhuma instrução UOPS_RETIRED_ANY no Skylake, apenas o cara “slots aposentados” que aparentemente é um domínio fundido.

O teste final ( uop-test xx ) é uma variante que Peter sugere que usa um cmp relativo ao RIP com o imediato, que se sabe não microfusar:

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

Os resultados mostram que os 2 uops extras por ciclo são captados pelos contadores emitidos e retirados (portanto, o teste pode diferenciar entre a fusão ocorrendo, e não).

Mais testes em outras arquiteturas são bem vindos! Você pode encontrar o código (copiado de Peter acima) no github .


[1] … e talvez algumas outras arquiteturas entre Skylake e Sandybridge, já que Peter só testou o SB e eu só testei o SKL.

Processadores Intel antigos sem um cache uop podem fazer a fusão, então talvez isso seja uma desvantagem do cache uop. Eu não tenho tempo para testar isso agora, mas vou adicionar um teste para a fusão uop na próxima vez que eu atualizar meus scripts de teste . Você já tentou com instruções FMA? Eles são as únicas instruções que permitem 3 dependencies de input em um uop sem uso.

Analisei agora os resultados dos testes para o Intel Sandy Bridge, o Ivy Bridge, o Haswell e o Broadwell. Eu não tive access para testar em um Skylake ainda. Os resultados são:

  • Instruções com endereçamento de dois registradores e três dependencies de input estão se fundindo corretamente. Eles pegam apenas uma input no cache de micro-operação, desde que não contenham mais do que 32 bits de dados (ou 2 * 16 bits).
  • É possível fazer instruções com quatro dependencies de input, usando instruções fundidas com multiplicação e adição em Haswell e Broadwell. Essas instruções ainda se fundem em uma única microinstrução e obtêm apenas uma input no cache de microinstruções.
  • Instruções com mais de 32 bits de dados, por exemplo, endereço de 32 bits e dados imediatos de 8 bits ainda podem se fundir, mas use duas inputs no cache de microoperação (a menos que os 32 bits possam ser compactados em um inteiro assinado de 16 bits)
  • Instruções com endereçamento relativo de rip e uma constante imediata não estão se fundindo, mesmo que tanto o offset quanto a constante imediata sejam muito pequenos.
  • Todos os resultados são idênticos nas quatro máquinas testadas.
  • Os testes foram realizados com meus próprios programas de teste usando os contadores de monitoramento de desempenho em loops que eram suficientemente pequenos para caber no cache de microinstruções.

Seus resultados podem ser devidos a outros fatores. Eu não tentei usar o IACA.