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
performance
assembly
optimization
x86
micro-optimization
balajimc55
fuente
fuente
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ándoloxor 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, usexor r10d,r10d
, notxor r10,r10
.Ejemplos de GP-integer:
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 quepxor
, peroxorps
necesita el puerto de ejecución 5 en Intel Nehalem, mientras quepxor
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
ypxor
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
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 / AVXvpcmpeqd
está rompiendo las depuraciones en muchos (aunque todavía necesita un uop para escribir los 1), pero AVX512vpternlogd
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.
¿Qué tiene de especial poner a cero modismos como xor en varios uarches?
Algunas CPU reconocen
sub same,same
como un lenguaje de puesta a cero comoxor
, pero todas las CPU que reconocen cualquier lenguaje de puesta a cero reconocenxor
. Solo utilíceloxor
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 demov 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 quexor
.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 axor
lo largomov
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 modoxor eax, eax
/inc al
/inc eax
evita la pena de-registro parcial usual que pre-IVB CPUs tiene. Incluso sinxor
, IvB solo necesita un uop de fusión cuandoAH
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):
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. Dadosetcc
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: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 elxor
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 consideressahf
/lahf
opushf
/popf
). IvB puede eliminarmovzx 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 lasmov
instrucciones regulares , por lo quemovzx
toma una unidad de ejecución y tiene una latencia distinta de cero, lo que hace que la prueba /setcc
/ seamovzx
peor quexor
/ 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. Usarmov reg, 0
/setcc
para poner a cero / romper dependencias es probablemente la mejor alternativa cuandoxor
/ 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 ventajasxor
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 quexor 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 comomov
funciona).xor
solo rompe las cadenas de dependencia en el caso especial donde src y dest son el mismo registro, razón por la cualmov
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 ambosmov
y luegoxor
-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.TL: DR:
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 usarxor
, pero a veces puede xor-zero antes de lo que establece las banderas si tiene un registro de repuesto.mov
-zero antes desetcc
es mejor para la latencia quemovzx reg32, reg8
después (excepto en Intel cuando puede elegir diferentes registros), pero peor tamaño de código.fuente
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 depopcnt/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 dicesxor 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.