¿Cómo funcionan exactamente los registros parciales en Haswell / Skylake? Escribir AL parece tener una falsa dependencia de RAX, y AH es inconsistente
Este bucle se ejecuta en una iteración por 3 ciclos en Intel Conroe/Merom, embotellado en imul
rendimiento como se esperaba. Pero en Haswell / Skylake, se ejecuta en una iteración por 11 ciclos, aparentemente porque setnz al
tiene una dependencia del último imul
.
; synthetic micro-benchmark to test partial-register renaming
mov ecx, 1000000000
.loop: ; do{
imul eax, eax ; a dep chain with high latency but also high throughput
imul eax, eax
imul eax, eax
dec ecx ; set ZF, independent of old ZF. (Use sub ecx,1 on Silvermont/KNL or P4)
setnz al ; ****** Does this depend on RAX as well as ZF?
movzx eax, al
jnz .loop ; }while(ecx);
Si setnz al
depende de rax
, la secuencia 3ximul/setcc/movzx forma una cadena de dependencias en bucle. Si no, cada uno setcc
/movzx
/3ximul
cadena es independiente, bifurcada de la dec
que actualiza el contador de bucle. El 11c por la iteración medida en HSW / SKL se explica perfectamente por un cuello de botella de latencia: 3x3c (imul) + 1c(lectura-modificación-escritura por setcc) + 1c(movzx dentro del mismo registro).
Off topic: evitar estos cuellos de botella (intencionales)
Iba por un comportamiento comprensible / predecible para aislar cosas de reg parcial, no un rendimiento óptimo.
Por ejemplo, xor
-zero / set-flags / setcc
es mejor de todos modos (en este caso, xor eax,eax
/ dec ecx
/ setnz al
). Que se rompe el dep en eax en todas las CPU (excepto la familia P6 temprana como PII y PIII), todavÃa evita penalizaciones de fusión de registros parciales, y ahorra 1c de latencia movzx
. También usa una uop de ALU menos en las CPU que manejan xor-zeroing en la etapa register-rename. Consulte ese enlace para obtener más información sobre el uso de xor-zeroing con setcc
.
Tenga en cuenta que AMD, Intel Silvermont/KNL, y P4, no hacen el cambio de nombre de registro parcial en absoluto. Es solo una caracterÃstica de las CPU de la familia Intel P6 y su descendiente, Intel Sandybridge-familia, pero parece estar siendo eliminado.
Gcc desafortunadamente tiende a usar cmp
/ setcc al
/ movzx eax,al
donde podrÃa haber utilizado xor
en lugar de movzx
(Godbolt compiler-explorer example), mientras que clang usa xor-zero/cmp/setcc a menos que combine múltiples condiciones booleanas como count += (a==b) | (a==~b)
.
La versión xor/dec/setnz se ejecuta a 3.0 c por iteración en Skylake, Haswell y Core2 (embotellado en imul
rendimiento). xor
- la puesta a cero rompe la dependencia de el antiguo valor de eax
en todas las CPU fuera de orden que no sean PPro/PII/PIII/early-Pentium-M (donde todavÃa evita penalizaciones de fusión de registros parciales pero no rompe el dep). La guÃa de microarquitectura de Agner Fog describe esto. Reemplazar el cero xor con mov eax,0
lo ralentiza a uno por 4.78 ciclos en Core2: 2-3c stall (en el front-end?) para insertar un uop de fusión de reg parcial cuando imul
se lee eax
después de setnz al
.
También, usé movzx eax, al
que vence mov-eliminación, al igual que mov rax,rax
hace. (IvB, HSW y SKL pueden renombrar movzx eax, bl
con 0 latencia, pero Core2 no puede). Esto hace que todo sea igual en Core2 / SKL, excepto por el comportamiento de registro parcial.
El comportamiento Core2 es consistente con La guÃa microarch de Agner Fog , pero el comportamiento HSW/SKL no lo es. De la sección 11.10 para Skylake, y lo mismo para los anteriores uarches de Intel:
Diferentes partes de un registro de propósito general se pueden almacenar en diferentes registros temporales para eliminar dependencias falsas.
Desafortunadamente no tiene tiempo para hacer pruebas detalladas para cada nuevo uarch para volver a probar las suposiciones, por lo que este cambio en el comportamiento se deslizó a través de las grietas.
Agner describe un uop de fusión que se inserta (sin estancamiento) para registros high8 (AH/BH/CH/DH) en Sandybridge a través de Skylake, y para low8/low16 en SnB. (Desafortunadamente he estado difundiendo información errónea en el pasado, y diciendo que Haswell puede fusionar AH gratis. Hojeé la sección Haswell de Agner demasiado rápido, y no noté el párrafo posterior sobre los registros high8. Hazme saber si ves mis comentarios incorrectos en otras publicaciones, para que pueda eliminarlos o agregar una corrección. Intentaré al menos encontrar y editar mis respuestas donde he dicho esto.)
Mis preguntas reales: ¿Cómo exactamente se comportan realmente los registros parciales en Skylake?
Es todo lo mismo de IvyBridge a Skylake, incluyendo la latencia extra high8?
El manual de optimización de Intel no es especÃfico sobre qué CPU tienen dependencias falsas para qué (aunque menciona que algunas CPU las tienen), y omite cosas como leer AH/BH/CH/DH (registros high8) agregando latencia adicional incluso cuando no se han modificado.
Si hay algún comportamiento de la familia P6 (Core2 / Nehalem) que la guÃa de microarquitectura de Agner Fog no describe, eso serÃa interesante también, pero probablemente deberÃa limitar el alcance de esta pregunta a solo Skylake o Sandybridge-familia.
Mis datos de prueba de Skylake , de poner %rep 4
secuencias cortas dentro de un pequeño bucle dec ebp/jnz
que ejecuta iteraciones de 100M o 1G. Medà los ciclos con Linux perf
de la misma manera que en mi respuesta aquÃ, en el mismo hardware (desktop Skylake i7 6700k).
A menos que se indique lo contrario, cada instrucción se ejecuta como 1 uop de dominio fusionado, utilizando una ALU puerto de ejecución. (Medido con ocperf.py stat -e ...,uops_issued.any,uops_executed.thread
). Esto detecta (ausencia de) eliminación de movimientos y uops de fusión adicionales.
Los casos de "4 por ciclo" son una extrapolación al caso infinitamente desenrollado. La sobrecarga de bucle ocupa parte del ancho de banda del front-end, pero cualquier cosa mejor que 1 por ciclo es una indicación de que el cambio de nombre de registro evitó la dependencia de salida escritura tras escritura, y que la uop no se maneja internamente como un lectura-modificación-escritura.
Escribir solo en AH : evita que el bucle se ejecute desde el búfer de bucle invertido (también conocido como el Detector de Flujo de bucle (LSD)). Los recuentos para lsd.uops
son exactamente 0 en HSW, y diminutos en SKL (alrededor de 1.8 k) y no escalan con el conteo de iteración de bucle. Probablemente esos recuentos son de algún código del núcleo. Cuando los bucles se ejecutan desde el LSD, lsd.uops ~= uops_issued
hasta dentro del ruido de medición. Algunos bucles alternan entre LSD o no-LSD (por ejemplo, cuando podrÃan no caber en la caché de uop si la decodificación comienza en el lugar equivocado), pero no me encontré con eso mientras probaba esto.
- repetido
mov ah, bh
y/omov ah, bl
se ejecuta a 4 por ciclo. Se necesita un ALU uop, por lo que no se elimina comomov eax, ebx
es. - repeated
mov ah, [rsi]
funciona a 2 por ciclo (cuello de botella de rendimiento de carga). - repetido
mov ah, 123
funciona a 1 por ciclo. (A dep-breakingxor eax,eax
dentro del bucle elimina el cuello de botella.) -
Repetido
setz ah
osetc ah
funciona a 1 por ciclo. (Un dep-breakingxor eax,eax
le permite un cuello de botella en el rendimiento p06 parasetcc
y la rama de bucle.)¿Por qué escribir
ah
con una instrucción que normalmente usarÃa una unidad de ejecución ALU tiene una dependencia falsa en el valor antiguo, mientras quemov r8, r/m8
no lo hace (para reg o memory src)? (¿Y qué pasa conmov r/m8, r8
? Seguramente no importa cuál de los dos opcodes uses para movimientos reg-reg.) Repeated
add ah, 123
funciona a 1 por ciclo, como previsto.- repetido
add dh, cl
funciona a 1 por ciclo. - repetido
add dh, dh
funciona a 1 por ciclo. - repetido
add dh, ch
funciona a 0.5 por ciclo. Leer [ABCD] H es especial cuando están "limpios" (en este caso, RCX no se ha modificado recientemente en absoluto).
TerminologÃa : Todas ellas dejan AH (o DH) " sucio", es decir, en necesidad de fusionar (con un uop de fusión) cuando se lee el resto del registro (o en algunos otros casos). es decir, que AH es renombrado por separado de RAX, si estoy entendiendo esto correctamente. " limpio " es lo contrario. Hay muchas maneras de limpiar un registro sucio, siendo la más simple inc eax
o mov eax, esi
.
Escribiendo solo a AL: Estos bucles se ejecutan desde el LSD: uops_issue.any
~= lsd.uops
.
- repetido
mov al, bl
funciona a 1 por ciclo. Una ruptura ocasional depxor eax,eax
por grupo permite un cuello de botella de ejecución de OOO en el rendimiento de uop, no en la latencia. - repetido
mov al, [rsi]
funciona a 1 por ciclo, como ALU micro-fundido + uop de la carga. (uops_issued = 4G + loop overhead,uops_executed = 8G + loop overhead). Un dep-breakingxor eax,eax
antes de un grupo de 4 le deja embotellamiento en 2 cargas por reloj. - repetido
mov al, 123
funciona a 1 por ciclo. - repetido
mov al, bh
funciona a 0.5 por ciclo. (1 por 2 ciclos). Leer [ABCD] H es especial. -
xor eax,eax
+ 6xmov al,bh
+dec ebp/jnz
: 2c por iter, cuello de botella en 4 uops por reloj para el front-end. - repetido
add dl, ch
funciona a 0.5 por ciclo. (1 por 2 ciclos). Leer [ABCD]H aparentemente crea latencia extra paradl
. - repetido
add dl, cl
funciona a 1 por ciclo.
Creo que una escritura a un reg bajo-8 se comporta como una mezcla de RMW en el reg completo, como serÃa add eax, 123
, pero no desencadena una fusión si ah
está sucio. Por lo tanto (aparte de ignorar AH
la fusión) se comporta de la misma manera que en las CPU que no hacen el cambio de nombre de reg parcial en absoluto. Parece que AL
nunca se cambia el nombre por separado de RAX
?
-
inc al
/inc ah
los pares pueden correr en paralelo. -
mov ecx, eax
inserta un uop fusionado siah
es "sucio", pero elmov
real es renombrado. Esto es lo que Agner Fog describe para IvyBridge y posteriores. - repetido
movzx eax, ah
se ejecuta en uno por 2 ciclos. (Lectura alta-8 registros después de escribir regs completo tiene latencia adicional.) -
movzx ecx, al
tiene cero latencia y no toma un puerto de ejecución en HSW y SKL. (Como lo que Agner Fog describe para IvyBridge, pero dice que HSW no cambia el nombre de movzx). -
movzx ecx, cl
tiene latencia 1c y toma un puerto de ejecución. (mov-elimination nunca funciona para elsame,same
case, solo entre diferentes registros arquitectónicos.)Un bucle que inserta un uop de fusión cada iteración no puede ejecutarse desde el LSD (búfer de bucle)?
No creo que haya nada especial en AL/AH/RAX vs.B*, C*, DL/DH/RDX. He probado algunos con regs parciales en otros registros (a pesar de que estoy mostrando sobre todo AL
/AH
para la consistencia), y nunca han notado ninguna diferencia.
¿Cómo podemos explicar todas estas observaciones con un modelo sensato de cómo funciona internamente la microarquitectura?
Relacionado: Partial flaglos problemas son diferentes de partial register issues. Ver INC instrucción vs ADD 1: ¿Importa? para algunas cosas súper raras con shr r32,cl
(e incluso shr r32,2
en Core2/Nehalem: no lea las banderas de un cambio que no sea por 1).
Ver también Problemas con ADC/SBB e INC/DEC en bucles estrechos en algunas CPU para cosas de bandera parcial en bucles adc
.
1 answers
Otras respuestas bienvenidos a abordar Sandybridge e IvyBridge con más detalle. No tengo acceso a ese hardware.
No he encontrado ninguna diferencia de comportamiento de registro parcial entre HSW y SKL. En Haswell y Skylake, todo lo que he probado hasta ahora es compatible con este modelo:
AL nunca es renombrado por separado de RAX (o r15b de r15). Asà que si nunca toca los registros high8 (AH / BH / CH / DH), todo se comporta exactamente como en una CPU sin parcial-cambio de nombre reg (por ejemplo, AMD).
El acceso de solo escritura a AL se fusiona con RAX, con una dependencia de RAX. Para cargas en AL, este es un ALU micro-fusionado + uop de carga que se ejecuta en p0156, que es una de las piezas más fuertes de evidencia de que realmente se está fusionando en cada escritura, y no solo haciendo una doble contabilidad de lujo como Agner especuló.
Agner (e Intel) dicen que Sandybridge puede requerir una fusión de uop para AL, por lo que probablemente sea renombrado por separado de RAX. Para SnB, El manual de optimización de Intel (sección 3.5.2.4 Paradas parciales del registro) dice
SnB (no necesariamente uarches posteriores) inserta un uop fusionado en los siguientes casos:
Después de una escritura a uno de los registros AH, BH, CH o DH y antes de una siguiente lectura de la forma de 2, 4 u 8 bytes del mismo registro. En en estos casos se inserta un micro-op de fusión. La inserción consume una asignación completa el ciclo en el que otros micro-ops no be allocated.
Después de un micro-op con un registro de destino de 1 o 2 bytes, que es no es una fuente de la instrucción (o la forma más grande del registro), y antes de una lectura siguiente de una forma de 2, 4 u 8 bytes de la misma registrar. En estos casos el micro-op de fusión es parte del flujo.
Creo que están diciendo que en SnB, add al,bl
RMW el RAX completo en lugar de renombrarlo por separado, porque una de las fuentes registers es (parte de) RAX. Mi conjetura es que esto no se aplica para una carga como mov al, [rbx + rax]
; rax
en un modo de direccionamiento probablemente no cuenta como fuente.
No he probado si high8 uops de fusión todavÃa tienen que emitir/renombrar por su cuenta en HSW/SKL. Eso harÃa que el impacto front-end equivaliera a 4 uops (ya que ese es el problema/renombrar el ancho de la tuberÃa).
- No hay manera de romper una dependencia que involucra AL sin escribir EAX/RAX.
xor al,al
no ayuda, y tampoco doesmov al, 0
. -
movzx ebx, al
tiene latencia cero (renombrado), y no necesita ninguna unidad de ejecución. (es decir, mov-elimination funciona en HSW y SKL). Desencadena la fusión de AH si está sucio, que supongo que es necesario para que funcione sin un ALU. Probablemente no sea una coincidencia que Intel eliminara el cambio de nombre de low8 en el mismo uarch que introdujo la eliminación de mov. (La guÃa de micro-arco de Agner Fog tiene un error aquÃ, diciendo que los movimientos extendidos a cero no se eliminan en HSW o SKL, solo IvB.) -
movzx eax, al
es no eliminado al renombrar. mov-eliminación en Intel nunca funciona para lo mismo, lo mismo.mov rax,rax
tampoco se elimina, a pesar de que no tiene que cero-extender nada. (Aunque no tendrÃa sentido darle soporte de hardware especial, porque es solo un no-op, a diferencia demov eax,eax
). De todos modos, prefiere moverse entre dos registros arquitectónicos separados cuando se extiende a cero, ya sea con unmov
de 32 bits o un 8 bitsmovzx
. -
movzx eax, bx
es no eliminado al renombrar en HSW o SKL. Tiene latencia de 1c y utiliza un ALU uop. El manual de optimización de Intel solo menciona la latencia cero para movzx de 8 bits (y señala quemovzx r32, high8
nunca se cambia el nombre).
High-8 regs se pueden renombrar por separado del resto del registro, y necesitan fusionar uops.
- Acceso de solo escritura a
ah
conmov ah, r8
omov ah, [mem]
cambie el nombre de AH, sin dependencia del valor antiguo. Estas son instrucciones que normalmente no necesitarÃan un uop de ALU (para la versión de 32 bits). - un RMW de AH (como
inc ah
) lo ensucia. -
setcc ah
depende de lo viejoah
, pero todavÃa lo ensucia. Creo quemov ah, imm8
es lo mismo, pero no he probado tantos casos de esquina.(Inexplicable: un bucle que implica
setcc ah
a veces puede correr desde el LSD, ver el buclercr
al final de este post. Tal vez mientrasah
esté limpio al final del bucle, puede ¿usar el LSD?).Si
ah
está sucio,setcc ah
se fusiona en el renombradoah
, en lugar de forzar una fusión enrax
. e. g.%rep 4
(inc al
/test ebx,ebx
/setcc ah
/inc al
/inc ah
) no genera uops de fusión, y solo se ejecuta en aproximadamente 8.7 c (latencia de 8inc al
ralentizada por conflictos de recursos de las uops paraah
. También elinc ah
/setcc ah
dep chain).Creo que lo que está pasando aquà es que
setcc r8
siempre se implementa como lectura-modificación-escritura. Intel probablemente decidió que no valÃa la pena tener una uop de solo escriturasetcc
para optimizar el casosetcc ah
, ya que es muy raro que el código generado por el compilador seasetcc ah
. (Pero vea el enlace godbolt en la pregunta: clang4.0 con-m32
lo hará.) La lectura de AX, EAX o RAX desencadena una uop de fusión (que toma el problema de front-end/renombrar el ancho de banda). Probablemente la RAT (Register Allocation Table) rastrea el estado high-8-dirty para la arquitectura R [ABCD]X, e incluso después de que una escritura a AH se retira, los datos de AH son almacenado en un registro fÃsico separado de RAX. Incluso con 256 NOPs entre escribir AH y leer EAX, hay un uop de fusión adicional. (ROB size = 224 en SKL, por lo que esto garantiza que el
mov ah, 123
fue retirado). Detectado con contadores de perf uops_issued / executed, que muestran claramente la diferencia.Read-modify-write of AL (e.g.
inc al
) se fusiona de forma gratuita, como parte de la uop de ALU. (Solo probado con unos pocos uops simples, comoadd
/inc
, nodiv r8
omul r8
). Una vez más, no hay fusión uop se activa incluso si AH está sucio.Solo escritura en EAX / RAX (como
lea eax, [rsi + rcx]
oxor eax,eax
) borra el estado AH-dirty (sin fusionar uop).- Solo escritura en AX (
mov ax, 1
) desencadena una fusión de AH primero. Supongo que en lugar de esta carcasa especial, funciona como cualquier otro RMW de AX / RAX. (TODO: testmov ax, bx
, aunque eso no deberÃa ser especial porque no está renombrado.) -
xor ah,ah
tiene una latencia de 1c, no es dep-breaking, y todavÃa necesita una ejecución portuario. - Leer y/o escribir de AL no fuerza una fusión, por lo que AH puede permanecer sucio (y ser utilizado independientemente en una cadena dep separada). (e. g.
add ah, cl
/add al, dl
puede ejecutarse a 1 por reloj (embotellado en agregar latencia).
Hacer AH sucio evita que un bucle se ejecute desde el LSD (el búfer de bucle), incluso cuando no hay uops de fusión. El LSD es cuando la CPU recicla uops en la cola que alimenta la etapa de edición/cambio de nombre. (Llamado el IDQ).
Insertar uops de fusión es un poco como insertar uops de stack-sync para el motor de pila. El manual de optimización de Intel dice que el LSD de SnB no puede ejecutar bucles con coincidencias push
/pop
, lo cual tiene sentido, pero implica que puede ejecutar bucles con balanceado push
/pop
. Eso no es lo que estoy viendo en SKL: incluso equilibrado push
/pop
evita correr desde el LSD (p. ej. push rax
/ pop rdx
/ times 6 imul rax, rdx
. (Puede haber una diferencia real entre el LSD de SnB y HSW / SKL: SnB puede simplemente "bloquear" los uops en el IDQ en lugar de repetirlos varias veces, por lo que un bucle de 5 uop tarda 2 ciclos en emitirse en lugar de 1.25. De todos modos, parece que HSW/SKL no puede usar el LSD cuando un registro high-8 está sucio, o cuando contiene uops de motor de pila.
Este comportamiento puede estar relacionado con un una errata en SKL:
Problema: Bajo condiciones microarquitectónicas complejas, bucles cortos de menos de 64 instrucciones que usan registros AH, BH, CH o DH, asà como sus correspondientes registros más amplios (por ejemplo, RAX, EAX o AX para AH) pueden causar un comportamiento impredecible del sistema. Esto solo puede suceder cuando ambos procesadores lógicos en el mismo procesador fÃsico están activos.
Esto también puede estar relacionado con la declaración del manual de optimización de Intel de que SnB al menos tiene que emita / cambie el nombre de una uop AH-merge en un ciclo por sà misma. Esa es una diferencia extraña para el front-end.
Mi registro del kernel de Linux dice microcode: sig=0x506e3, pf=0x2, revision=0x84
.
El paquete intel-ucode
de Arch Linux solo proporciona la actualización, debe editar los archivos de configuración para que realmente se cargue. Asà que mi prueba de Skylake fue en un i7-6700k con la revisión de microcódigo 0x84, que no incluye la solución para SKL150. Coincide con el comportamiento de Haswell en todos los casos que probé, IIRC. (por ejemplo, tanto Haswell como mi SKL puede ejecutar el setne ah
/ add ah,ah
/ rcr ebx,1
/ mov eax,ebx
bucle del LSD). He habilitado HT (que es una condición previa para que se manifieste SKL150), pero estaba probando en un sistema en su mayorÃa inactivo, por lo que mi hilo tenÃa el núcleo para sà mismo.
Con el microcódigo actualizado, el LSD está completamente desactivado para todo todo el tiempo, no solo cuando los registros parciales están activos. lsd.uops
es siempre exactamente cero, incluso para programas reales no bucles sintéticos. Errores de hardware (en lugar de errores de microcódigo) a menudo requiere deshabilitar una caracterÃstica completa para corregirla. Esta es la razón por la que SKL-avx512 (SKX) es informado de que no tiene un búfer de bucle invertido. Afortunadamente esto no es un problema de rendimiento: el aumento del rendimiento de la caché uop de SKL sobre Broadwell casi siempre puede mantenerse al dÃa con el problema/cambio de nombre.
Latencia extra AH/BH/CH/DH:
- Leer AH cuando no está sucio (renombrado por separado) agrega un ciclo adicional de latencia para ambos operandos. por ejemplo,
add bl, ah
tiene una latencia de 2c desde la entrada BL hasta salida BL, por lo que puede agregar latencia a la ruta crÃtica incluso si RAX y AH no forman parte de ella. (He visto este tipo de latencia adicional para el otro operando antes, con latencia vectorial en Skylake, donde un retardo int/float "contamina" un registro para siempre. Escribe eso.)
Esto significa desempaquetar bytes con movzx ecx, al
/ movzx edx, ah
tiene latencia extra vs.movzx
/shr eax,8
/movzx
, pero aún mejor rendimiento.
-
Leer AH cuando es sucio no añade cualquier latencia. (
add ah,ah
oadd ah,dh
/add dh,ah
tener una latencia de 1c por adición). No he hecho muchas pruebas para confirmar esto en muchos casos de esquina.Hipótesis: un valor high8 sucio se almacena en la parte inferior de un registro fÃsico . Leer un high8 limpio requiere un cambio para extraer bits [15:8], pero leer un high8 sucio solo puede tomar bits [7: 0] de un registro fÃsico como una lectura normal de registro de 8 bits.
La latencia adicional no significa una reducción del rendimiento. Este el programa puede ejecutarse a 1 iter por 2 relojes, a pesar de que todas las instrucciones add
tienen una latencia de 2c (de la lectura de DH, que no se modifica.)
global _start
_start:
mov ebp, 100000000
.loop:
add ah, dh
add bh, dh
add ch, dh
add al, dh
add bl, dh
add cl, dh
add dl, dh
dec ebp
jnz .loop
xor edi,edi
mov eax,231 ; __NR_exit_group from /usr/include/asm/unistd_64.h
syscall ; sys_exit_group(0)
Performance counter stats for './testloop':
48.943652 task-clock (msec) # 0.997 CPUs utilized
1 context-switches # 0.020 K/sec
0 cpu-migrations # 0.000 K/sec
3 page-faults # 0.061 K/sec
200,314,806 cycles # 4.093 GHz
100,024,930 branches # 2043.675 M/sec
900,136,527 instructions # 4.49 insn per cycle
800,219,617 uops_issued_any # 16349.814 M/sec
800,219,014 uops_executed_thread # 16349.802 M/sec
1,903 lsd_uops # 0.039 M/sec
0.049107358 seconds time elapsed
Algunos cuerpos de bucle de prueba interesantes :
%if 1
imul eax,eax
mov dh, al
inc dh
inc dh
inc dh
; add al, dl
mov cl,dl
movzx eax,cl
%endif
Runs at ~2.35c per iteration on both HSW and SKL. reading `dl` has no dep on the `inc dh` result. But using `movzx eax, dl` instead of `mov cl,dl` / `movzx eax,cl` causes a partial-register merge, and creates a loop-carried dep chain. (8c per iteration).
%if 1
imul eax, eax
imul eax, eax
imul eax, eax
imul eax, eax
imul eax, eax ; off the critical path unless there's a false dep
%if 1
test ebx, ebx ; independent of the imul results
;mov ah, 123 ; dependent on RAX
;mov eax,0 ; breaks the RAX dependency
setz ah ; dependent on RAX
%else
mov ah, bl ; dep-breaking
%endif
add ah, ah
;; ;inc eax
; sbb eax,eax
rcr ebx, 1 ; dep on add ah,ah via CF
mov eax,ebx ; clear AH-dirty
;; mov [rdi], ah
;; movzx eax, byte [rdi] ; clear AH-dirty, and remove dep on old value of RAX
;; add ebx, eax ; make the dep chain through AH loop-carried
%endif
La versión setcc (con el %if 1
) tiene latencia de bucle 20c, y se ejecuta desde el LSD a pesar de que tiene setcc ah
y add ah,ah
.
00000000004000e0 <_start.loop>:
4000e0: 0f af c0 imul eax,eax
4000e3: 0f af c0 imul eax,eax
4000e6: 0f af c0 imul eax,eax
4000e9: 0f af c0 imul eax,eax
4000ec: 0f af c0 imul eax,eax
4000ef: 85 db test ebx,ebx
4000f1: 0f 94 d4 sete ah
4000f4: 00 e4 add ah,ah
4000f6: d1 db rcr ebx,1
4000f8: 89 d8 mov eax,ebx
4000fa: ff cd dec ebp
4000fc: 75 e2 jne 4000e0 <_start.loop>
Performance counter stats for './testloop' (4 runs):
4565.851575 task-clock (msec) # 1.000 CPUs utilized ( +- 0.08% )
4 context-switches # 0.001 K/sec ( +- 5.88% )
0 cpu-migrations # 0.000 K/sec
3 page-faults # 0.001 K/sec
20,007,739,240 cycles # 4.382 GHz ( +- 0.00% )
1,001,181,788 branches # 219.276 M/sec ( +- 0.00% )
12,006,455,028 instructions # 0.60 insn per cycle ( +- 0.00% )
13,009,415,501 uops_issued_any # 2849.286 M/sec ( +- 0.00% )
12,009,592,328 uops_executed_thread # 2630.307 M/sec ( +- 0.00% )
13,055,852,774 lsd_uops # 2859.456 M/sec ( +- 0.29% )
4.565914158 seconds time elapsed ( +- 0.08% )
Inexplicable: corre del LSD, a pesar de que ensucia. (Al menos creo lo hace. TODO: intente agregar algunas instrucciones que hagan algo con eax
antes de que mov eax,ebx
lo borre.)
Pero con mov ah, bl
, se ejecuta en 5.0 c por iteración (imul
cuello de botella de rendimiento) en ambos HSW/SKL. (La tienda/recarga comentada también funciona, pero SKL tiene un reenvÃo de tienda más rápido que HSW, y es de latencia variable...)
# mov ah, bl version
5,009,785,393 cycles # 4.289 GHz ( +- 0.08% )
1,000,315,930 branches # 856.373 M/sec ( +- 0.00% )
11,001,728,338 instructions # 2.20 insn per cycle ( +- 0.00% )
12,003,003,708 uops_issued_any # 10275.807 M/sec ( +- 0.00% )
11,002,974,066 uops_executed_thread # 9419.678 M/sec ( +- 0.00% )
1,806 lsd_uops # 0.002 M/sec ( +- 3.88% )
1.168238322 seconds time elapsed ( +- 0.33% )
Observe que ya no se ejecuta desde el LSD.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-22 18:35:39