c - tag - ¿Por qué usa el movl en lugar de push?
posicionarse en google maps (5)
Preste atención a este código:
#include <stdio.h>
void a(int a, int b, int c)
{
char buffer1[5];
char buffer2[10];
}
int main()
{
a(1,2,3);
}
después de esto :
gcc -S a.c
Ese comando muestra nuestro código fuente en ensamblador.
Ahora podemos ver en la función principal, nunca usamos el comando "push" para empujar los argumentos de la función en la pila. y usó "movel" en lugar de eso
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
movl $3, 8(%esp)
movl $2, 4(%esp)
movl $1, (%esp)
call a
leave
¿Por que sucede? ¿Qué diferencia hay entre ellos?
¿Es esto en OS X por casualidad? Leí en algún lugar que requiere que el puntero de pila esté alineado en los límites de 16 bytes. Eso podría explicar este tipo de generación de código.
Encontré el artículo: http://blogs.embarcadero.com/eboling/2009/05/20/5607
El conjunto de instrucciones Pentium no tiene una instrucción para empujar una constante en la pila. Por lo tanto, utilizar push
sería lento: el programa tendría que poner la constante en un registro y empujar el registro:
...
movl $1, %eax
pushl %eax
...
Así que el compilador detecta que usar movl
es más rápido. Supongo que puedes intentar llamar a tu función con una variable en lugar de una constante:
int x;
scanf("%d", &x); // make sure x is not a constant
a(x, x, x);
Ese código simplemente pone las constantes (1, 2, 3) directamente en las posiciones de desplazamiento desde el puntero de pila (actualizado) (esp). El compilador está eligiendo hacer el "empuje" manualmente con el mismo resultado.
"push" establece los datos y actualiza el puntero de pila. En este caso, el compilador está reduciendo eso a solo una actualización del puntero de pila (vs. tres). Un experimento interesante sería intentar cambiar la función "a" para tomar solo un argumento y ver si el patrón de instrucción cambia.
Esto es lo que el manual de gcc tiene que decir al respecto:
-mpush-args
-mno-push-args
Use PUSH operations to store outgoing parameters. This method is shorter and usually
equally fast as method using SUB/MOV operations and is enabled by default.
In some cases disabling it may improve performance because of improved scheduling
and reduced dependencies.
-maccumulate-outgoing-args
If enabled, the maximum amount of space required for outgoing arguments will be
computed in the function prologue. This is faster on most modern CPUs because of
reduced dependencies, improved scheduling and reduced stack usage when preferred
stack boundary is not equal to 2. The drawback is a notable increase in code size.
This switch implies -mno-push-args.
Al parecer, -maccumulate-outgoing-args
está habilitado de forma predeterminada, anulando -mpush-args
. La compilación explícita con -mno-accumulate-outgoing-args
vuelve al método PUSH
, aquí.
gcc realiza todo tipo de optimizaciones, incluida la selección de instrucciones basadas en la velocidad de ejecución de la CPU en particular que se está optimizando. Notará que cosas como x *= n
menudo se reemplazan por una mezcla de SHL, ADD y / o SUB, especialmente cuando n es una constante; mientras que MUL solo se usa cuando el tiempo de ejecución promedio (y las huellas de caché / etc.) de la combinación de SHL-ADD-SUB excedería el de MUL, o n
no es una constante (y, por lo tanto, usar bucles con shl-add-sub) venir más costoso).
En caso de argumentos de función: MOV puede ser paralelizado por hardware, mientras que PUSH no puede. (El segundo PUSH debe esperar a que termine el primer PUSH debido a la actualización del registro esp). En el caso de los argumentos de función, los MOV pueden ejecutarse en paralelo.