Todas las siguientes instrucciones hacen lo mismo: poner %eax
a cero. ¿Cuál es la forma óptima (que requiere menos ciclos de máquina)?
xorl %eax, %eax
mov $0, %eax
andl $0, %eax
Todas las siguientes instrucciones hacen lo mismo: poner %eax
a cero. ¿Cuál es la forma óptima (que requiere menos ciclos de máquina)?
xorl %eax, %eax
mov $0, %eax
andl $0, %eax
Respuestas:
TL; Resumen de DR : xor same, same
es la mejor opción para todas las CPU . Ningún otro método tiene ninguna ventaja sobre él, y tiene al menos alguna ventaja sobre cualquier otro método. Es recomendado oficialmente por Intel y AMD, y lo que hacen los compiladores. En modo de 64 bits, siga utilizándolo xor r32, r32
, porque escribir un registro de 32 bits pone ceros a los 32 superiores . xor r64, r64
es una pérdida de un byte, porque necesita un prefijo REX.
Incluso peor que eso, Silvermont solo reconoce xor r32,r32
como de ruptura de depósito, no de tamaño de operando de 64 bits. Por lo tanto, incluso cuando se requiera un prefijo REX porque está poniendo a cero r8..r15, use xor r10d,r10d
, notxor r10,r10
.
Ejemplos de GP-integer:
xor eax, eax ; RAX = 0. Including AL=0 etc.
xor r10d, r10d ; R10 = 0
xor edx, edx ; RDX = 0
; small code-size alternative: cdq ; zero RDX if EAX is already zero
; SUB-OPTIMAL
xor rax,rax ; waste of a REX prefix, and extra slow on Silvermont
xor r10,r10 ; bad on Silvermont (not dep breaking), same as r10d everywhere else because a REX prefix is still needed for r10d or r10.
mov eax, 0 ; doesn't touch FLAGS, but not faster and takes more bytes
and eax, 0 ; false dependency. (Microbenchmark experiments might want this)
sub eax, eax ; same as xor on most but not all CPUs; bad on Silvermont for example.
xor al, al ; false dep on some CPUs, not a zeroing idiom. Use xor eax,eax
mov al, 0 ; only 2 bytes, and probably better than xor al,al *if* you need to leave the rest of EAX/RAX unmodified
Por lo general, es mejor poner a cero un registro vectorial pxor xmm, xmm
. Eso es típicamente lo que hace gcc (incluso antes de usarlo con instrucciones FP).
xorps xmm, xmm
puede tener sentido. Es un byte más corto que pxor
, pero xorps
necesita el puerto de ejecución 5 en Intel Nehalem, mientras que pxor
puede ejecutarse en cualquier puerto (0/1/5). (La latencia de retardo de bypass 2c de Nehalem entre entero y FP generalmente no es relevante, porque la ejecución fuera de orden generalmente puede ocultarla al comienzo de una nueva cadena de dependencia).
En las microarquitecturas de la familia SnB, ninguna versión de xor-zeroing necesita siquiera un puerto de ejecución. En AMD y pre-Nehalem P6 / Core2 Intel, xorps
y pxor
se manejan de la misma manera (como instrucciones de vectores enteros).
El uso de la versión AVX de una instrucción vectorial de 128b también pone a cero la parte superior del registro, por lo que vpxor xmm, xmm, xmm
es una buena opción para poner a cero YMM (AVX1 / AVX2) o ZMM (AVX512), o cualquier extensión de vector futura. vpxor ymm, ymm, ymm
Sin embargo, no se necesitan bytes adicionales para codificar y funciona igual en Intel, pero más lento en AMD antes de Zen2 (2 uops). La puesta a cero de AVX512 ZMM requeriría bytes adicionales (para el prefijo EVEX), por lo que debería preferirse la puesta a cero de XMM o YMM.
Ejemplos de XMM / YMM / ZMM
# Good:
xorps xmm0, xmm0 ; smallest code size (for non-AVX)
pxor xmm0, xmm0 ; costs an extra byte, runs on any port on Nehalem.
xorps xmm15, xmm15 ; Needs a REX prefix but that's unavoidable if you need to use high registers without AVX. Code-size is the only penalty.
# Good with AVX:
vpxor xmm0, xmm0, xmm0 ; zeros X/Y/ZMM0
vpxor xmm15, xmm0, xmm0 ; zeros X/Y/ZMM15, still only 2-byte VEX prefix
#sub-optimal AVX
vpxor xmm15, xmm15, xmm15 ; 3-byte VEX prefix because of high source reg
vpxor ymm0, ymm0, ymm0 ; decodes to 2 uops on AMD before Zen2
# Good with AVX512
vpxor xmm15, xmm0, xmm0 ; zero ZMM15 using an AVX1-encoded instruction (2-byte VEX prefix).
vpxord xmm30, xmm30, xmm30 ; EVEX is unavoidable when zeroing zmm16..31, but still prefer XMM or YMM for fewer uops on probable future AMD. May be worth using only high regs to avoid needing vzeroupper in short functions.
# Good with AVX512 *without* AVX512VL (e.g. KNL / Xeon Phi)
vpxord zmm30, zmm30, zmm30 ; Without AVX512VL you have to use a 512-bit instruction.
# sub-optimal with AVX512 (even without AVX512VL)
vpxord zmm0, zmm0, zmm0 ; EVEX prefix (4 bytes), and a 512-bit uop. Use AVX1 vpxor xmm0, xmm0, xmm0 even on KNL to save code size.
Consulte ¿La puesta a cero de vxorps en AMD Jaguar / Bulldozer / Zen es más rápida con registros xmm que ymm? y
¿Cuál es la forma más eficaz de borrar uno o varios registros ZMM en Knights Landing?
Semi-relacionado: La forma más rápida de establecer el valor __m256 en todos los bits UNO y
Establecer todos los bits en el registro de la CPU en 1 de manera eficiente también cubre los registros de k0..7
máscara AVX512 . SSE / AVX vpcmpeqd
está rompiendo las depuraciones en muchos (aunque todavía necesita un uop para escribir los 1), pero AVX512 vpternlogd
para los registros de ZMM ni siquiera es una depuradora. Dentro de un bucle, considere copiar de otro registro en lugar de volver a crearlos con un uop ALU, especialmente con AVX512.
Pero poner a cero es barato: xor-poner a cero un registro xmm dentro de un bucle suele ser tan bueno como copiar, excepto en algunas CPU AMD (Bulldozer y Zen) que tienen eliminación de mov para registros vectoriales pero aún necesitan un uop de ALU para escribir ceros para xor -poner a cero.
Algunas CPU reconocen sub same,same
como un lenguaje de puesta a cero como xor
, pero todas las CPU que reconocen cualquier lenguaje de puesta a cero reconocenxor
. Solo utilícelo xor
para no tener que preocuparse por qué CPU reconoce qué idioma de puesta a cero.
xor
(siendo un lenguaje de reducción a cero reconocido, a diferencia de mov reg, 0
) tiene algunas ventajas obvias y algunas sutiles (lista resumida, luego las ampliaré):
mov reg,0
. (Todas las CPU)Un tamaño de código de máquina más pequeño (2 bytes en lugar de 5) siempre es una ventaja: una densidad de código más alta conduce a menos pérdidas de caché de instrucciones, y una mejor captura de instrucciones y potencialmente decodificar el ancho de banda.
El beneficio de no usar una unidad de ejecución para xor en microarquitecturas de la familia Intel SnB es menor, pero ahorra energía. Es más probable que importe en SnB o IvB, que solo tienen 3 puertos de ejecución ALU. Haswell y versiones posteriores tienen 4 puertos de ejecución que pueden manejar instrucciones ALU enteras, incluyendo mov r32, imm32
, por lo que con una toma de decisiones perfecta por parte del programador (lo que no siempre sucede en la práctica), HSW aún podría sostener 4 uops por reloj incluso cuando todos necesitan ALU puertos de ejecución.
Consulte mi respuesta a otra pregunta sobre la reducción a cero de los registros para obtener más detalles.
La publicación del blog de Bruce Dawson que Michael Petch vinculó (en un comentario sobre la pregunta) señala que xor
se maneja en la etapa de registro y cambio de nombre sin necesidad de una unidad de ejecución (cero uops en el dominio no fusionado), pero se perdió el hecho de que todavía es un uop en el dominio fusionado. Las CPU modernas de Intel pueden emitir y retirar 4 uops de dominio fusionado por reloj. De ahí viene el límite de 4 ceros por reloj. La mayor complejidad del hardware de cambio de nombre de registro es solo una de las razones para limitar el ancho del diseño a 4. (Bruce ha escrito algunas publicaciones de blog muy excelentes, como su serie sobre matemáticas FP y problemas de redondeo x87 / SSE , que yo hago altamente recomendado).
En las CPU de la familia AMD Bulldozer , se mov immediate
ejecuta en los mismos puertos de ejecución de enteros EX0 / EX1 que xor
. mov reg,reg
también se puede ejecutar en AGU0 / 1, pero eso es solo para copia de registro, no para configuración desde inmediatos. Así que yo sepa, en la única ventaja de AMD a xor
lo largo mov
es la codificación más corta. También podría ahorrar recursos de registro físico, pero no he visto ninguna prueba.
Los modismos de puesta a cero reconocidos evitan penalizaciones por registros parciales en las CPU Intel que cambian el nombre de los registros parciales por separado de los registros completos (familias P6 y SnB).
xor
se etiquetar el registro como teniendo las partes superior a cero , de modo xor eax, eax
/ inc al
/ inc eax
evita la pena de-registro parcial usual que pre-IVB CPUs tiene. Incluso sin xor
, IvB solo necesita un uop de fusión cuando AH
se modifican los 8 bits ( ) altos y luego se lee todo el registro, y Haswell incluso lo elimina.
De la guía de microarquía de Agner Fog, pág. 98 (sección Pentium M, a la que se hace referencia en secciones posteriores, incluido SnB):
El procesador reconoce el XOR de un registro consigo mismo al establecerlo en cero. Una etiqueta especial en el registro recuerda que la parte alta del registro es cero, de modo que EAX = AL. Esta etiqueta se recuerda incluso en un bucle:
; Example 7.9. Partial register problem avoided in loop xor eax, eax mov ecx, 100 LL: mov al, [esi] mov [edi], eax ; No extra uop inc esi add edi, 4 dec ecx jnz LL
(de pg82): El procesador recuerda que los 24 bits superiores de EAX son cero siempre que no se produzca una interrupción, predicción errónea u otro evento de serialización.
pg82 de guía que también confirma que mov reg, 0
se no se reconoce como un lenguaje de puesta a cero, al menos en P6 principios de diseños como PIII o PM. Me sorprendería mucho si gastaran transistores en detectarlo en CPU posteriores.
xor
establece banderas , lo que significa que debe tener cuidado al probar las condiciones. Dado setcc
que, lamentablemente, solo está disponible con un destino de 8 bits , por lo general debe tener cuidado para evitar multas por registro parcial.
Hubiera sido bueno si x86-64 hubiera reutilizado uno de los códigos de operación eliminados (como AAM) para un bit 16/32/64 setcc r/m
, con el predicado codificado en el campo de 3 bits del registro de origen del campo r / m (la forma en que algunas otras instrucciones de un solo operando las utilizan como bits de código de operación). Pero no hicieron eso, y eso no ayudaría para x86-32 de todos modos.
Idealmente, debería usar xor
/ set flags / setcc
/ read full register:
...
call some_func
xor ecx,ecx ; zero *before* the test
test eax,eax
setnz cl ; cl = (some_func() != 0)
add ebx, ecx ; no partial-register penalty here
Esto tiene un rendimiento óptimo en todas las CPU (sin paradas, fusiones o falsas dependencias).
Las cosas son más complicadas cuando no desea xor antes de una instrucción de colocación de banderas . por ejemplo, quiere bifurcarse en una condición y luego establecer cc en otra condición desde los mismos indicadores. por ejemplo cmp/jle
, sete
y que o bien no tienen un registro de repuesto, o si desea mantener el xor
fuera de la ruta de código no-tomado por completo.
No existen modismos de puesta a cero reconocidos que no afecten a las banderas, por lo que la mejor opción depende de la microarquitectura de destino. En Core2, la inserción de un uop combinado puede provocar un bloqueo de 2 o 3 ciclos. Parece ser más barato en SnB, pero no pasé mucho tiempo tratando de medir. El uso de mov reg, 0
/ setcc
tendría una penalización significativa en las CPU Intel más antiguas y aún sería algo peor en las Intel más nuevas.
Usar setcc
/ movzx r32, r8
es probablemente la mejor alternativa para las familias Intel P6 y SnB, si no puede xor-zero antes de la instrucción de configuración de banderas. Eso debería ser mejor que repetir la prueba después de un xor-zeroing. (Ni siquiera consideres sahf
/ lahf
o pushf
/ popf
). IvB puede eliminar movzx r32, r8
(es decir, manejarlo con cambio de nombre de registro sin unidad de ejecución o latencia, como xor-zeroing). Haswell y versiones posteriores solo eliminan las mov
instrucciones regulares , por lo que movzx
toma una unidad de ejecución y tiene una latencia distinta de cero, lo que hace que la prueba / setcc
/ sea movzx
peor que xor
/ prueba / setcc
, pero al menos tan buena como la prueba / mov r,0
/ setcc
(y mucho mejor en las CPU más antiguas).
Usar setcc
/ movzx
sin poner a cero primero es malo en AMD / P4 / Silvermont, porque no rastrean los departamentos por separado para los subregistros. Habría un depósito falso sobre el valor anterior del registro. Usar mov reg, 0
/ setcc
para poner a cero / romper dependencias es probablemente la mejor alternativa cuando xor
/ test / setcc
no es una opción.
Por supuesto, si no necesita que setcc
la salida sea más ancha que 8 bits, no necesita poner a cero nada. Sin embargo, tenga cuidado con las dependencias falsas en CPU que no sean P6 / SnB si elige un registro que recientemente fue parte de una cadena de dependencia larga. (Y tenga cuidado de causar un bloqueo parcial del registro o un uop adicional si llama a una función que podría guardar / restaurar el registro del que está usando parte).
and
con un cero inmediato no tiene una carcasa especial como independiente del valor anterior en cualquier CPU que conozca, por lo que no rompe las cadenas de dependencia. No tiene ventajas xor
y muchas desventajas.
Es útil solo para escribir microbenchmarks cuando desea una dependencia como parte de una prueba de latencia, pero desea crear un valor conocido reduciendo a cero y agregando.
Ver http://agner.org/optimize/ para más detalles microarch , incluyendo el que los modismos de puesta a cero se reconocen como romper la dependencia (por ejemplo, sub same,same
es en algunas pero no todas las CPU, mientras que xor same,same
se reconoce en absoluto.) mov
Hace romper la cadena de dependencia en el valor de edad del registro (independientemente del valor de la fuente, cero o no, porque así es como mov
funciona). xor
solo rompe las cadenas de dependencia en el caso especial donde src y dest son el mismo registro, razón por la cual mov
se deja fuera de la lista de interruptores de dependencia especialmente reconocidos. (Además, porque no se reconoce como un modismo de reducción a cero, con los otros beneficios que conlleva).
Curiosamente, el diseño más antiguo de P6 (PPro a Pentium III) no reconoció xor
-zeroing como un interruptor de dependencia, solo como un lenguaje de puesta a cero con el propósito de evitar paradas de registro parcial , por lo que en algunos casos valió la pena usar ambos mov
y luego xor
-poner a cero en ese orden para romper el dep y luego a cero de nuevo + establecer el bit de etiqueta interno que los bits altos son cero para EAX = AX = AL.
Véase el ejemplo 6.17 de Agner Fog. en su pdf de microarch. Él dice que esto también se aplica a P2, P3 e incluso (¿temprano?) PM. Un comentario en la publicación del blog vinculada dice que solo PPro tuvo esta supervisión, pero yo probé en Katmai PIII, y @Fanael probó en un Pentium M, y ambos descubrimos que no rompía una dependencia para una latencia. -cadena enlazada imul
. Esto confirma los resultados de Agner Fog, desafortunadamente.
Si realmente hace que su código sea más agradable o guarda instrucciones, entonces claro, cero con mov
para evitar tocar las banderas, siempre y cuando no introduzca un problema de rendimiento que no sea el tamaño del código. Evitar las banderas que golpean es la única razón sensata para no usar xor
, pero a veces puede xor-zero antes de lo que establece las banderas si tiene un registro de repuesto.
mov
-zero antes de setcc
es mejor para la latencia que movzx reg32, reg8
después (excepto en Intel cuando puede elegir diferentes registros), pero peor tamaño de código.
mov reg, src
también rompe las cadenas de depuración para las CPU OO (independientemente de que src sea imm32 [mem]
, u otro registro). Esta ruptura de dependencias no se menciona en los manuales de optimización porque no es un caso especial que solo ocurre cuando src y dest son el mismo registro. Es siempre sucede por las instrucciones que no dependen de su dest. (excepto por la implementación de Intel de popcnt/lzcnt/tzcnt
tener un depósito falso en el destino)
mov
gratis, solo latencia cero. La parte de "no tomar un puerto de ejecución" generalmente no es importante. El rendimiento del dominio fusionado puede ser fácilmente el cuello de botella, especialmente. con cargas o almacenes en la mezcla.
xor r64, r64
no solo desperdicia un byte. Como dices xor r32, r32
es la mejor opción especialmente con KNL. Consulte la sección 15.7 "Casos especiales de independencia" en este manual de microarca si desea leer más.