¿Cuál es la mejor manera de establecer un registro a cero en el ensamblaje x86: xor, mov o y?

Todas las siguientes instrucciones hacen lo mismo: establezca %eax en cero. ¿Qué camino es óptimo (que requiere menos ciclos de máquina)?

 xorl %eax, %eax mov $0, %eax andl $0, %eax 

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 oficialmente recomendado por Intel y AMD. En el modo de 64 bits, siga usando xor r32, r32 , ya que escribir un registro de 32 bits ceros en los 32 superiores . xor r64, r64 es un desperdicio de un byte, porque necesita un prefijo REX.

Poner a cero un registro vectorial generalmente se realiza mejor con pxor xmm, xmm . Eso es típicamente lo que hace gcc (incluso antes de usarlo con las instrucciones de 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 puede ocultarla al principio de una nueva cadena de dependencia).

En las microarchitectures de la familia SnB, ni el sabor de xor-zeroing necesita 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 entero vector).

El uso de la versión AVX de una instrucción vector 128b también vpxor xmm, xmm, xmm 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 vectorial futura. vpxor ymm, ymm, ymm no toma ningún byte adicional para codificar, y ejecuta el mismo. La puesta a cero del AVX512 ZMM requeriría bytes adicionales (para el prefijo EVEX), por lo que debería preferirse la puesta a cero XMM o YMM.


Algunas CPU reconocen sub same,same que un modismo de cero como xor , pero todas las CPU que reconocen cualquier modismo de cero reconocen xor . Simplemente use xor para no tener que preocuparse por qué CPU reconoce qué idioma de puesta a cero.

xor (siendo una expresión idiomática reconocida, a diferencia de mov reg, 0 ) tiene algunas ventajas obvias y algunas sutiles (lista resumida, luego ampliaré esas):

  • tamaño de código más pequeño que mov reg,0 . (Todas las CPU)
  • evita penalizaciones de registro parcial para código posterior. (Familia Intel P6 y familia SnB).
  • no usa una unidad de ejecución, ahorrando energía y liberando recursos de ejecución. (Familia Intel SnB)
  • uop más pequeño (sin datos inmediatos) deja espacio en la línea de caché uop para instrucciones cercanas para pedir prestado si es necesario. (Familia Intel SnB).
  • no usa entradas arriba en el archivo de registro físico . (Intel SnB-family (y P4) al menos, posiblemente AMD también, ya que utilizan un diseño PRF similar en lugar de mantener el estado de registro en el ROB como microarchitectures de la familia Intel P6).

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 un menor número de fallas en la caché de la instrucción y una mejor captación de la instrucción y, potencialmente, la desencoding del ancho de banda.


El beneficio de no usar una unidad de ejecución para xor en las microarchitectures 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 más tarde tienen 4 puertos de ejecución que pueden manejar instrucciones enteras de ALU, incluyendo mov r32, imm32 , por lo que con perfecta toma de decisiones por el progtwigdor (lo que no ocurre en la práctica), HSW aún podría sostener 4 uops por reloj incluso cuando todos necesitan puertos de ejecución.

Vea mi respuesta a otra pregunta sobre la puesta a cero de registros para obtener más detalles.

La entrada de blog de Bruce Dawson que Michael Petch vinculó (en un comentario sobre la pregunta) señala que xor se maneja en la etapa de 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 sigue siendo uno uop en el dominio fusionado. Las CPU Intel modernas pueden emitir y retirar 4 uops de dominio fusionado por reloj. De ahí viene el límite de 4 ceros por reloj. La creciente 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 excelentes, como su serie sobre matemática FP y x87 / SSE / redondeo , que hago altamente recomendado).


En las CPU AMD Bulldozer-family , mov immediate ejecuta mov immediate en los mismos puertos de ejecución enteros EX0 / EX1 que xor . mov reg,reg también se puede ejecutar en AGU0 / 1, pero eso es solo para la copia de registros, no para la configuración de inmediatos. Así que AFAIK, en AMD la única ventaja de xor sobre mov es la encoding más corta. También podría guardar recursos de registros físicos, pero no he visto ninguna prueba.


Los modismos de puesta a cero reconocidos evitan las penalizaciones de registro parcial en las CPU Intel, que renombran los registros parciales por separado de los registros completos (familias P6 y SnB).

xor etiquetará el registro como que tiene las partes superiores puestas a cero , por lo que xor eax, eax / inc al / inc eax evita la penalización habitual de registro parcial que tienen las CPU pre-IvB. Incluso sin xor , IvB solo necesita fusionar uop cuando los 8bits altos ( AH ) se modifican y luego se lee todo el registro, y Haswell incluso lo elimina.

De la guía de microarch de Agner Fog, página 98 (sección Pentium M, a la que se hace referencia en secciones posteriores, incluida 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 

(desde pg82): el procesador recuerda que los 24 bits superiores de EAX son cero siempre que no obtenga interrupciones, errores de predicción u otros eventos de serialización.

pg82 de esa guía también confirma que mov reg, 0 no se reconoce como una expresión idiomática de puesta a cero, al menos en los primeros diseños de P6 como PIII o PM. Me sorprendería mucho si gastaran transistores para detectarlo en CPUs posteriores.


xor establece banderas , lo que significa que debe tener cuidado al probar las condiciones. Dado que setcc desafortunadamente solo está disponible con un destino de 8 bits , por lo general debe tener cuidado para evitar penalizaciones de registro parcial.

Hubiera sido bueno si x86-64 reutilizara uno de los códigos de setcc r/m eliminados (como AAM) para un setcc setcc r/m 16/32/64 bits, con el predicado codificado en el campo fuente-registro de 3 bits del r / m campo (la forma en que otras instrucciones de un solo operando los 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, debe 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 uops o dependencias falsas).

Las cosas son más complicadas cuando no quiere xor antes de una instrucción de ajuste de banderas . por ejemplo, quiere ramificar en una condición y luego establecer en otra condición de las mismas banderas. por ejemplo, cmp/jle , sete , y usted no tiene un registro extra, o desea mantener el xor fuera de la ruta del código no tomado por completo.

No hay expresiones idiomáticas de puesta a cero reconocidas que no afecten a las banderas, por lo que la mejor opción depende de la microarchitecture objective. En Core2, insertar un uop de fusión podría causar un locking de 2 o 3 ciclos. Parece ser más barato en SnB, pero no perdí mucho tiempo tratando de medir. Usando mov reg, 0 / setcc tendría una penalización significativa en las CPUs Intel más antiguas, y aún sería algo peor en la Intel más nueva.

Usando 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 registro-cambio de nombre sin unidad de ejecución o latencia, como xor-zeroing). Haswell y luego solo eliminan las instrucciones mov normales, entonces movzx toma una unidad de ejecución y tiene una latencia distinta de cero, haciendo que test / setcc / movzx peor que xor / test / setcc , pero al menos tan bueno como test / mov r,0 / setcc (y mucho mejor en CPUs antiguas).

Usar setcc / movzx sin cero primero es malo en AMD / P4 / Silvermont, porque no rastrean deps por separado para los sub-registros. Habría un depósito falso en el valor anterior del registro. Usando mov reg, 0 / setcc para zeroing / dependency-breaking es probablemente la mejor alternativa cuando xor / test / setcc no es una opción.

Por supuesto, si no necesita que la salida de setcc sea ​​más ancha que 8 bits, no necesita poner en cero nada. Sin embargo, tenga cuidado con las dependencias falsas en las CPU que no sean P6 / SnB si elige un registro que recientemente formó parte de una larga cadena de dependencias. (Y tenga cuidado de no causar un registro parcial o un error extra si llama a una función que podría guardar / restaurar el registro del que está utilizando parte de).


and con un cero inmediato no se trata de un caso especial, independiente del valor anterior en cualquier CPU que conozco, por lo que no interrumpe las cadenas de dependencia. No tiene ventajas sobre xor , y muchas desventajas.

Vea http://agner.org/optimize/ para documentación de microarch, incluyendo qué modismos de puesta a cero se reconocen como interrupción de dependencia (p. Ej., sub same,same es para algunas pero no todas las CPU, mientras que xor same,same es lo xor same,same para todos). mov rompe la cadena de dependencia en el valor anterior del registro (independientemente del valor de origen, cero o no, porque así es como funciona el mov ). xor solo rompe cadenas de dependencia en el caso especial donde src y dest son el mismo registro, por lo que mov queda fuera de la lista de interruptores de dependencia especialmente reconocidos. (Además, porque no se reconoce como un modismo cero, con los otros beneficios que conlleva).

Curiosamente, el diseño P6 más antiguo (PPro) no reconoció xor zeroing como un interruptor de dependencia, solo como una expresión idiomática de puesta a cero con el fin de evitar puestos de registro parcial, por lo que en algunos casos valió la pena usar ambos . (Consulte el Ejemplo 6.17 de Agner Fog en su microarch pdf. Afirma que esto también se aplica a P2, P3 e incluso (¿principios?) De PM, pero soy escéptico de eso. Un comentario en la publicación de blog vinculada dice que solo fue PPro. que tenía esta supervisión. Parece realmente poco probable que existieran varias generaciones de la familia P6 sin reconocer el xor-zeroing como un interruptor de dep.)


Si realmente hace que su código sea más agradable o guarda instrucciones, entonces seguro, mov a 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. Sin embargo, evitar las banderas de trinchar es la única razón sensata para no usar xor .