assembly - que - qué son los modos de fusión
Micro fusión y modos de direccionamiento (4)
He encontrado algo inesperado (para mí) usando el Intel® Architecture Code Analyzer (IACA).
La siguiente instrucción usando direccionamiento
[base+index]
addps xmm1, xmmword ptr [rsi+rax*1]
no se fusiona de acuerdo con IACA.
Sin embargo, si uso
[base+offset]
como este
addps xmm1, xmmword ptr [rsi]
IACA informa que se fusiona.
La sección 2-11 del manual de referencia de optimización de Intel ofrece lo siguiente como un ejemplo "de micro-operaciones micro fusionadas que pueden ser manejadas por todos los decodificadores"
FADD DOUBLE PTR [RDI + RSI*8]
y
el manual de ensamblaje de optimización de Agner Fog
también da ejemplos de fusión micro-op usando direccionamiento
[base+index]
.
Consulte, por ejemplo, la Sección 12.2 "El mismo ejemplo en Core2".
Entonces, ¿cuál es la respuesta correcta?
Ahora he revisado los resultados de las pruebas para Intel Sandy Bridge, Ivy Bridge, Haswell y Broadwell. Todavía no he tenido acceso a la prueba en un Skylake. Los resultados son:
- Las instrucciones con direccionamiento de dos registros y tres dependencias de entrada están fusionando bien. Solo toman una entrada en el caché de microoperaciones siempre que no contengan más de 32 bits de datos (o 2 * 16 bits).
- Es posible hacer instrucciones con cuatro dependencias de entrada, utilizando instrucciones fusionadas de multiplicar y agregar en Haswell y Broadwell. Estas instrucciones aún se fusionan en una única microoperación y solo toman una entrada en la caché de la microoperación.
- Las instrucciones con más de 32 bits de datos, por ejemplo, la dirección de 32 bits y los datos inmediatos de 8 bits aún pueden fusionarse, pero use dos entradas en el caché de microoperación (a menos que los 32 bits se puedan comprimir en un entero con signo de 16 bits)
- Las instrucciones con direccionamiento relativo a rasgadura y una constante inmediata no se fusionan, incluso si el desplazamiento y la constante inmediata son muy pequeños.
- Todos los resultados son idénticos en las cuatro máquinas probadas.
- Las pruebas se realizaron con mis propios programas de prueba utilizando los contadores de monitoreo de rendimiento en bucles que eran lo suficientemente pequeños como para caber en el caché de microoperación.
Sus resultados pueden deberse a otros factores. No he tratado de usar la IACA.
En los decodificadores y uop-cache, el modo de direccionamiento no afecta a la micro fusión (excepto que una instrucción con un operando inmediato no puede fusionar un modo de direccionamiento relativo a RIP).
Pero algunas combinaciones de uop y modo de direccionamiento no pueden permanecer micro fusionadas en el ROB (en el núcleo fuera de servicio), por lo que las CPUs Intel SnB-family "se deslaminan" cuando es necesario, en algún momento antes del problema / renombrar etapa. Para el rendimiento del problema y el tamaño de la ventana fuera de orden (tamaño ROB), lo que importa es el recuento de UOP de dominio fusionado después de la deslaminación.
El manual de optimización de Intel describe la eliminación de la laminación para Sandybridge en la Sección 2.3.2.4: Micro-op Queue y Loop Stream Detector (LSD) , pero no describe los cambios para microarquitecturas posteriores.
ACTUALIZACIÓN: Ahora el manual de Intel tiene una sección detallada para describir la no laminación para Haswell. Ver sección 2.3.5 Deslaminación. Y una breve descripción de SandyBridge se encuentra en la sección 2.4.2.4.
Las reglas , lo mejor que puedo decir de los experimentos en SnB, HSW y SKL:
- SnB (y supongo también IvB): los modos de direccionamiento indexado siempre están sin laminar, otros permanecen micro fusionados. IACA es (¿mayormente?) Correcto.
-
HSW, SKL: solo mantienen una instrucción ALU indexada micro fusionada si tiene 2 operandos y trata el registro dst como lectura-modificación-escritura.
Aquí "operandos" incluye banderas, lo que significa que
adc
ycmov
no se fusionan. La mayoría de las instrucciones codificadas por VEX tampoco se fusionan, ya que generalmente tienen tres operandos (por lo quepaddb xmm0, [rdi+rbx]
fusionan perovpaddb xmm0, xmm0, [rdi+rbx]
no). Finalmente, la instrucción ocasional de 2 operandos donde el primer operando es solo escritura, comopabsb xmm0, [rax + rbx]
tampoco se fusiona. IACA está equivocado, aplicando las reglas de SnB.
Relacionado: los modos de direccionamiento simples (no indexados) son los únicos que puede manejar la unidad de dirección de tienda dedicada en el puerto 7 (Haswell y posterior), por lo que aún es potencialmente útil evitar los modos de direccionamiento indexado para tiendas.
(Un buen truco para esto es abordar su dst con un único registro, pero src con
dst+(initial_src-initial_dst)
. Entonces solo tiene que incrementar el registro dst dentro de un bucle).
Tenga en cuenta que algunas instrucciones nunca se fusionan en absoluto (incluso en los decodificadores / uop-cache).
por ejemplo,
shufps xmm, [mem], imm8
o
vinsertf128 ymm, ymm, [mem], imm8
, siempre son 2 uops en SnB a través de Skylake, a pesar de que sus versiones de origen de registro son solo 1 uop.
Esto es típico para instrucciones con un operando de control imm8 más los operandos dest / src1, src2 register / memory usuales, pero hay algunos otros casos.
por ejemplo,
PSRLW/D/Q xmm,[mem]
(recuento de desplazamiento de vector desde un operando de memoria) no se fusiona en micro, y tampoco PMULLD.
Vea también esta publicación en el blog de Agner Fog para una discusión sobre los límites de rendimiento de problemas en HSW / SKL cuando lee muchos registros: una gran cantidad de micro fusión con modos de direccionamiento indexado puede conducir a ralentizaciones frente a las mismas instrucciones con menos operandos de registro: uno- registrar modos de direccionamiento e inmediatos. Todavía no conocemos la causa, pero sospecho que hay algún tipo de límite de lectura de registros, tal vez relacionado con la lectura de muchos registros en frío del PRF.
Casos de prueba, números de mediciones reales : todos estos micro fusibles en los decodificadores, AFAIK, incluso si luego no están 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
Instrucciones de tres entradas que HSW / SKL puede tener para deslaminar
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.
Supongo que Broadwell se comporta como Skylake para adc / cmov.
Es extraño que HSW no lamine la fuente de memoria ADC y CMOV. Tal vez Intel no logró cambiar eso de SnB antes de que llegaran a la fecha límite para enviar a Haswell.
La tabla de información de Agner dice
cmovcc r,m
adc r,m
no se fusiona en absoluto en HSW / SKL, pero eso no coincide con mis experimentos.
El recuento de ciclos que estoy midiendo coincide con el recuento de problemas de UOP de dominio fusionado, para un cuello de botella de 4 uops / reloj.
Esperemos que vuelva a verificar eso y corrija las tablas.
ALU entero de memoria-dest :
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.)
Sí, así es,
adc [rdi],eax
/
dec ecx
/
jnz
ejecuta
más rápido
que el mismo bucle con
add
lugar de
adc
en SKL.
No intenté usar diferentes direcciones, ya que claramente a SKL no le gustan las reescrituras repetidas de la misma dirección (latencia de reenvío de la tienda más alta de lo esperado. Vea también
esta publicación acerca de que la repetición de la tienda / recarga en la misma dirección es más lenta de lo esperado en SKL
.
El
adc
destino de memoria tiene muchas uops porque la familia Intel P6 (y aparentemente la familia SnB) no puede mantener las mismas entradas TLB para todas las uops de una instrucción multi-uop, por lo que
necesita una uop adicional para solucionar el problema -case donde se completa la carga y la adición, y luego la tienda falla, pero el insn no puede reiniciarse simplemente porque CF ya se ha actualizado
.
Serie interesante de comentarios de Andy Glew (@krazyglew).
Presumiblemente, la fusión en los decodificadores y la deslaminación nos ahorran más tarde de la
necesidad de una microcódigo ROM
para producir más de 4 uops de dominio fusionado a partir de una sola instrucción para
adc [base+idx], reg
.
Por qué los laminados SnB-family :
Sandybridge simplificó el formato interno de UOP para ahorrar energía y transistores (junto con hacer el cambio principal al uso de un archivo de registro físico, en lugar de mantener los datos de entrada / salida en el ROB).
Las CPU de la familia SnB solo permiten un número limitado de registros de entrada para un uop de dominio fusionado en el núcleo fuera de servicio.
Para SnB / IvB, ese límite es de 2 entradas (incluidas las banderas).
Para HSW y versiones posteriores, el límite es de 3 entradas para una uop.
No estoy seguro de si el destino de memoria
add
y
adc
están aprovechando de eso, o si Intel tuvo que sacar a Haswell por la puerta con algunas instrucciones
Nehalem y versiones anteriores tienen un límite de 2 entradas para un uop de dominio no fusionado, pero el ROB aparentemente puede rastrear uops micro fusionados con 3 registros de entrada (el operando, la base y el índice sin registro de memoria).
Por lo tanto, las tiendas indexadas y las instrucciones de carga ALU + aún pueden decodificarse de manera eficiente (no tener que ser el primer uop en un grupo), y no ocupar espacio adicional en el caché de uop, pero de lo contrario las ventajas de la micro fusión se han ido esencialmente para el ajuste bucles apretados. La "deslaminación" ocurre antes del núcleo fuera de servicio del ancho de emisión / retiro de 4 dominios fusionados por ciclo . Los contadores de rendimiento de dominio fusionado (uops_issued / uops_retired.retire_slots) cuentan uops de dominio fusionado después de la laminación.
La descripción de Intel del renombrador ( Sección 2.3.3.1: Renombrador ) implica que es la etapa de emisión / cambio de nombre lo que realmente hace la deslaminación, por lo que las unidades destinadas a la deslaminación aún pueden estar micro fusionadas en el 28/56/64 fusionado -dominio uop problema cola / loop-buffer (también conocido como el IDQ).
TODO: prueba esto. Haga un bucle que apenas debe caber en el búfer de bucle. Cambie algo para que uno de los uops no se lamine antes de emitirse, y vea si todavía se ejecuta desde el búfer de bucle (LSD), o si todos los uops ahora se recuperan de la caché de uop (DSB). Hay contadores de rendimiento para rastrear de dónde provienen los uops, por lo que esto debería ser fácil.
TODO más difícil: si se produce la deslaminación entre la lectura de la caché de uop y la adición al IDQ, pruebe si alguna vez puede reducir el ancho de banda de la caché de uop. O si la deslaminación ocurre justo en la etapa del problema, ¿puede dañar el rendimiento del problema? (es decir, ¿cómo maneja los uops sobrantes después de emitir los primeros 4)?
(Consulte la versión anterior de esta respuesta para algunas conjeturas basadas en el ajuste de algún código LUT, con algunas notas en
vpgatherdd
son aproximadamente
vpgatherdd
más ciclos que un bucle
pinsrw
).
Pruebas experimentales en SnB
Los números HSW / SKL se midieron en un i5-4210U y un i7-6700k.
Ambos tenían HT habilitado (pero el sistema estaba inactivo, por lo que el hilo tenía todo el núcleo para sí mismo).
Ejecuté los mismos binarios estáticos en ambos sistemas, Linux 4.10 en SKL y Linux 4.8 en HSW, usando
ocperf.py
.
(La computadora portátil HSW montó en NFS el escritorio / hogar de mi SKL).
Los números de SnB se midieron como se describe a continuación, en un i5-2500k que ya no funciona.
Confirmado mediante pruebas con contadores de rendimiento para uops y ciclos.
Encontré
una tabla de eventos de PMU para Intel Sandybridge
, para usar con el comando
perf
de Linux.
(Desafortunadamente, el rendimiento estándar no tiene nombres simbólicos para la mayoría de los eventos de PMU específicos de hardware, como uops). Lo utilicé para una
respuesta reciente
.
ocperf.py
proporciona nombres simbólicos para estos eventos de PMU específicos de uarch
, por lo que no tiene que buscar tablas.
Además, el mismo nombre simbólico funciona en múltiples uarches.
No lo sabía cuando escribí esta respuesta por primera vez.
Para probar la micro fusión de uop, construí un programa de prueba que tiene un cuello de botella en el límite de dominio fusionado de 4 uops por ciclo de las CPU Intel.
Para evitar cualquier disputa sobre el puerto de ejecución, muchos de estos uops son
nop
s, que aún permanecen en la caché de uop y pasan por la tubería de la misma manera que cualquier otro uop, excepto que no se envían a un puerto de ejecución.
(Un movimiento
xor x, same
o eliminado, sería lo mismo).
Programa de prueba:
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
También descubrí que el ancho de banda de uop fuera del búfer de bucle no es un constante 4 por ciclo, si el bucle no es un múltiplo de 4 uops.
(es decir, es
abc
,
abc
, ...; no
abca
,
bcab
, ...).
El documento de microarchivo de Agner Fog desafortunadamente no estaba claro sobre esta limitación del búfer de bucle.
Consulte
¿Se reduce el rendimiento al ejecutar bucles cuyo recuento de uop no es un múltiplo del ancho del procesador?
para más investigación sobre HSW / SKL.
SnB puede ser peor que HSW en este caso, pero no estoy seguro y todavía no tengo hardware SnB en funcionamiento.
Quería mantener la macro fusión (comparar y ramificar) fuera de la imagen, así que usé
nop
entre
dec
y la ramificación.
nop
4
nop
s, por lo que con micro fusión, el bucle sería de 8 uops y llenaría la tubería con 2 ciclos por 1 iteración.
En la otra versión del bucle, usando modos de direccionamiento de 2 operandos que no se fusionan, el bucle tendrá 10 uops de dominio fusionado y se ejecutará en 3 ciclos.
Resultados de mi Intel Sandybridge de 3.3GHz (i5 2500k). No hice nada para lograr que el gobernador cpufreq acelere la velocidad del reloj antes de la prueba, porque los ciclos son ciclos cuando no interactúa con la memoria. He agregado anotaciones para los eventos de contador de rendimiento que tuve que ingresar en hexadecimal.
prueba del modo de direccionamiento 1-reg: sin 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
probar el modo de direccionamiento de 2 registros: con un 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
Por lo tanto, ambas versiones ejecutaron 80 millones de instrucciones y enviaron 60 millones de uops a los puertos de ejecución.
(
or
con una fuente de memoria que se envía a una ALU para el
or
, y un puerto de carga para la carga, independientemente de si estaba micro fusionada o no en el resto de la tubería.
nop
no se envía a un puerto de ejecución en absoluto .) Del mismo modo, ambas versiones retiran 100 millones de dominios no fusionados, porque los 40 millones de nops cuentan aquí.
La diferencia está en los contadores para el dominio fusionado.
- La versión de la dirección de 1 registro solo emite y retira 80 millones de uops de dominio fusionado. Esto es lo mismo que el número de instrucciones. Cada entrada se convierte en una uop de dominio fusionado.
- La versión de la dirección de 2 registros emite 100 millones de dominios fusionados. Esto es lo mismo que el número de uops de dominio no fusionado, lo que indica que no ocurrió micro fusión.
Sospecho que solo vería una diferencia entre UOPS_ISSUED y UOPS_RETIRED (espacios de retiro utilizados) si las predicciones erróneas de la sucursal llevaron a que se cancelaran los uops después de la emisión, pero antes del retiro.
Y finalmente, el impacto en el rendimiento es real. La versión sin fusible tomó 1,5 veces más ciclos de reloj. Esto exagera la diferencia de rendimiento en comparación con la mayoría de los casos reales. El ciclo tiene que ejecutarse en un número entero de ciclos, y los 2 uops adicionales lo empujan de 2 a 3. A menudo, un adicional de 2 uops de dominio fusionado hará menos diferencia. Y potencialmente no hay diferencia, si el código es embotellado por algo que no sea 4-fused-domain-uops-per-cycle.
Aún así, el código que hace muchas referencias de memoria en un bucle podría ser más rápido si se implementa con una cantidad moderada de desenrollar e incrementar múltiples punteros que se usan con direccionamiento simple
[base + immediate offset]
, en lugar de usar
[base + index]
modos de direccionamiento.
otras cosas
Pariente RIP con un inmediato no puede micro-fusible . Las pruebas de Agner Fog muestran que este es el caso incluso en los decodificadores / uop-cache, por lo que nunca se fusionan en primer lugar (en lugar de estar sin laminar).
IACA se equivoca y afirma que ambos microfusibles:
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 hace micro fusible (y permanece fusionado) cuando no hay inmediato, por ejemplo:
or eax, dword [rel mydata] ; fused counters != unfused counters, i.e. micro-fusion happens
La micro fusión no aumenta la latencia de una instrucción . La carga puede emitirse antes de que la otra entrada esté lista.
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
Este bucle se ejecuta a 5 ciclos por iteración, debido a la cadena de dep
eax
.
No más rápido que una secuencia de
or eax, [rsi + 0 + rdi]
, o
mov ebx, [rsi + 0 + rdi] / or eax, ebx
.
(Las versiones no fusionadas y
mov
ejecutan el mismo número de uops). La comprobación de programación / dep ocurre en el dominio no fusionado.
Los uops recién emitidos entran en el planificador (también conocido como Reservation Station (RS)) y también en el ROB.
Abandonan el programador después del envío (también conocido como ser enviado a una unidad de ejecución), pero permanecen en el ROB hasta la jubilación.
Entonces, la ventana fuera de orden para ocultar la latencia de carga es al menos del tamaño del planificador (
54 uops de dominio no fusionado en Sandybridge, 60 en Haswell
, 97 en Skylake).
La micro fusión no tiene un atajo para la base y el desplazamiento es el mismo registro.
Un bucle con
or eax, [mydata + rdi+4*rdi]
(donde rdi está puesto a cero) ejecuta tantos uops y ciclos como el bucle con
or eax, [rsi+rdi]
.
Este modo de direccionamiento podría usarse para iterar sobre una matriz de estructuras de tamaño impar que comienzan en una dirección fija.
Probablemente, esto nunca se usa en la mayoría de los programas, por lo que no sorprende que Intel no haya gastado transistores para permitir que este caso especial de modos de 2 registros se fusione.
(Y Intel lo documenta como "modos de direccionamiento indexado" de todos modos, donde se necesita un registro y un factor de escala).
La macro fusión
de un
cmp
/
jcc
o
dec
/
jcc
crea una uop que permanece como una sola uop incluso en el dominio no fusionado.
dec / nop / jge
aún puede ejecutarse en un solo ciclo, pero son tres uops en lugar de uno.
Los procesadores Intel más antiguos sin un caché uop pueden hacer la fusión, por lo que tal vez esto sea un inconveniente del caché uop. No tengo tiempo para probar esto en este momento, pero agregaré una prueba para uop fusion la próxima vez que actualice mis scripts de prueba . ¿Has probado con las instrucciones de FMA? Son las únicas instrucciones que permiten 3 dependencias de entrada en un uop no fusionado.
Nota: desde que escribí esta respuesta, Peter también probó Haswell y Skylake e integró los resultados en la respuesta aceptada anteriormente (en particular, la mayoría de las mejoras que atribuyo a Skylake a continuación parecen haber aparecido realmente en Haswell). Debería ver esa respuesta para el resumen del comportamiento entre las CPU y esta respuesta (aunque no es incorrecta) es principalmente de interés histórico.
Mi prueba indica que en Skylake al menos 1 , el procesador fusiona completamente incluso los modos de direccionamiento complejos, a diferencia de Sandybridge.
Es decir, las versiones 1-arg y 2-arg del código publicado anteriormente por Peter se ejecutan en el mismo número de ciclos, con el mismo número de uops enviados y retirados.
Mis resultados:
Estadísticas del contador de rendimiento 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
Estadísticas del contador de rendimiento 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
Estadísticas del contador de rendimiento 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
No encontré ninguna instrucción UOPS_RETIRED_ANY en Skylake, solo el tipo de "ranuras retiradas" que aparentemente es de dominio fusionado.
La prueba final (
uop-test xx
) es una variante que Peter sugiere que utiliza un
cmp
relativo a RIP con inmediato, que se sabe que no se microfunde:
.loop_riprel
cmp dword [rel mydata], 1
cmp dword [rel mydata], 2
dec ecx
nop
nop
nop
nop
jg .loop_riprel
Los resultados muestran que los 2 uops adicionales por ciclo son recogidos por los contadores emitidos y retirados (por lo tanto, la prueba puede diferenciar entre la fusión que ocurre y no).
¡Más pruebas en otras arquitecturas son bienvenidas! Puede encontrar el código (copiado de Peter arriba) en github .
[1] ... y quizás algunas otras arquitecturas entre Skylake y Sandybridge, ya que Peter solo probó SB y yo solo probé SKL.