assembly - ensamblador - LEA o ADD instrucción?
emu8086 (4)
Cuando estoy a mano, generalmente elijo el formulario
lea eax, [eax+4]
Sobre la forma ..
add eax, 4
He oído que lea es una instrucción de "reloj 0" (como NOP), mientras que "agregar" no lo es. Sin embargo, cuando miro el ensamblador producido por el compilador, a menudo veo el último formulario utilizado en lugar del primero. Soy lo suficientemente inteligente como para confiar en el compilador, así que ¿alguien puede arrojar algo de luz sobre cuál es mejor? ¿Cuál es más rápido? ¿Por qué el compilador elige la última forma sobre la primera?
Soy lo suficientemente inteligente como para confiar en el compilador, así que ¿alguien puede arrojar algo de luz sobre cuál es mejor?
Sí un poco. En primer lugar, estoy tomando esto del siguiente mensaje: https://groups.google.com/group/bsdnt-devel/msg/23a48bb18571b9a6
En este mensaje, un desarrollador optimiza un ensamblaje que escribí muy mal para ejecutarlo increíblemente rápido en los procesadores Intel Core 2. Como fondo de este proyecto, es una biblioteca bsd bignum en la que yo y algunos otros desarrolladores hemos participado.
En este caso, todo lo que se optimiza es la suma de dos matrices que se ven así: uint64_t* x, uint64_t* y
. Cada "miembro" o miembro de la matriz representa parte del bignum; el proceso básico es iterar sobre él comenzando desde el miembro menos significativo, agregar el par hacia arriba y continuar hacia arriba, pasando el acarreo (cualquier desbordamiento) hacia arriba cada vez. adc
hace por usted en un procesador (no es posible acceder a la bandera de transporte desde CI, no creo).
En esa pieza de código, se usa una combinación de lea something, [something+1]
y jrcxz
, que aparentemente son más eficientes que el par de add something, size
jnz
/ add something, size
que previamente pudimos haber usado. Sin embargo, no estoy seguro si esto se descubrió como resultado de simplemente probar diferentes instrucciones. Tendría que preguntar.
Sin embargo, en un mensaje posterior, se mide en un chip AMD y no funciona tan bien.
También me han dado la oportunidad de entender que las diferentes operaciones funcionan de forma diferente en diferentes procesadores. Sé, por ejemplo, que el proyecto GMP detecta procesadores que usan cpuid
y los cpuid
en diferentes rutinas de ensamblaje basadas en diferentes arquitecturas, por ejemplo, core2
, nehalem
.
La pregunta que debe hacerse es si su compilador produce una salida optimizada para su arquitectura de CPU. El compilador de Intel, por ejemplo, es conocido por hacer esto, por lo que podría valer la pena medir el rendimiento y ver qué resultado produce.
LEA no es más rápido que la instrucción ADD, la velocidad de ejecución es la misma.
Pero LEA a veces ofrece más que ADD . Si necesitamos una suma / multiplicación simple y rápida en combinación con un segundo registro, LEA puede acelerar la ejecución del programa. Desde el otro lado, el LEA no afecta a las banderas de la CPU, por lo que no hay posibilidad de detección de desbordamiento.
Puede ejecutar una instrucción lea en el mismo ciclo de reloj como una operación de suma, pero si usa lea y suma, ¡puede realizar una suma de tres operandos en un solo ciclo! Si usaría dos operaciones adicionales que solo podrían realizarse en 2 ciclos de reloj:
mov eax, [esp+4] ; get a from stack
mov edx, [esp+8] ; get b from stack
mov ecx, [esp+12] ; get c from stack
lea eax, [eax+edx] ; add a and b in the adress decoding/fetch stage of the pipeline
add eax, ecx ; Add c + eax in the execution stage of the pipeline
ret 12
Una diferencia significativa entre LEA
y ADD
en CPU x86 es la unidad de ejecución que realmente realiza la instrucción. Las CPUs modernas x86 son superescalares y tienen múltiples unidades de ejecución que operan en paralelo, con la tubería alimentándolas de alguna manera como round-robin (puestos de barra). La cosa es que LEA
es procesada por (una de) la (s) unidad (es) que trata el direccionamiento (que ocurre en una etapa temprana de la tubería), mientras que ADD
va a la (s) ALU (aritmética / unidad lógica) y la tubería. Eso significa que una CPU x86 superescalar puede ejecutar simultáneamente una LEA
y una instrucción aritmética / lógica.
El hecho de que LEA
atraviesa la lógica de generación de direcciones en lugar de las unidades aritméticas es también la razón por la que solía llamarse "relojes cero"; no requiere tiempo para ejecutarse porque la generación de la dirección ya ha ocurrido en el momento en que se ejecutará / se ejecutará.
No es gratis , ya que la generación de direcciones es un paso en la línea de ejecución, pero no tiene una sobrecarga de ejecución. Y no ocupa una ranura en la (s) tubería (s) ALU.
Editar: para aclarar, LEA
no es gratis . Incluso en las CPU que no lo implementan a través de la unidad aritmética, lleva tiempo ejecutarlas debido a la instrucción decode / dispatch / retire y / u otras etapas de pipeline por las que pasan todas las instrucciones. El tiempo necesario para hacer LEA
solo ocurre en una etapa diferente de la tubería para las CPU que lo implementan a través de la generación de direcciones.