2010-12-26 1 views
17

prestare attenzione a questo codice:perché usa il movl invece di premere?

#include <stdio.h> 
void a(int a, int b, int c) 
{ 
    char buffer1[5]; 
    char buffer2[10]; 
} 

int main() 
{ 
    a(1,2,3); 
} 

dopo che:

gcc -S a.c 

che comandano mostra nostro codice sorgente in assembly.

ora possiamo vedere nella funzione principale, non usiamo mai il comando "push" per spingere gli argomenti di una funzione nello stack. e ha usato "movel" invece di quello

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 

perché succede? che differenza c'è tra loro?

risposta

16

Ecco ciò che il manuale di GCC ha da dire in proposito:

-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. 

Apparentemente -maccumulate-outgoing-args è abilitato di default, ignorando -mpush-args. La compilazione esplicita con -mno-accumulate-outgoing-args ripristina il metodo PUSH, qui.

+4

Una domanda molto migliore sarebbe il motivo per cui questa opzione generatrice '-maccumulate-outgoing-args' non viene automaticamente disabilitata da' -Os'. –

+0

@ R .. Quindi sai perché? – Tony

+0

@Tony: ovviamente, perché quando si decide quale dei molti (~ 200) flag di ottimizzazione abilitare/disabilitare per ciascuna opzione -O specifica, a volte le cose scivolano attraverso le fessure. – ninjalj

8

Questo codice inserisce direttamente le costanti (1, 2, 3) in posizioni sfalsate dal puntatore dello stack (aggiornato) (esp). Il compilatore sta scegliendo di fare il "push" manualmente con lo stesso risultato.

"push" imposta entrambi i dati e aggiorna il puntatore dello stack. In questo caso, il compilatore lo sta riducendo a un solo aggiornamento del puntatore dello stack (rispetto a tre). Un esperimento interessante sarebbe provare a cambiare la funzione "a" per prendere solo un argomento e vedere se il modello di istruzioni cambia.

+0

Perché dovresti inserire prima la costante in un registro? x86 supporta la spinta delle costanti immediate – Necrolis

+0

@Necrolis: abbastanza corretto. Modificato. Grazie. –

0

Il set di istruzioni Pentium non ha un'istruzione per spingere una costante nello stack. Quindi, utilizzando push sarebbe lento: il programma avrebbe dovuto mettere la costante in un registro e spingere il registro:

... 
movl $1, %eax 
pushl %eax 
... 

Così il compilatore rileva che l'utilizzo di movl è più veloce. Credo che si può provare a chiamare la funzione con una variabile al posto di una costante:

int x; 
scanf("%d", &x); // make sure x is not a constant 
a(x, x, x); 
+6

Il push di una costante è stato supportato dal 80286. Forse gcc ha il default di generare codice 8086? –

+1

Sembra che la mia conoscenza del set di istruzioni x86 sia un po 'obsoleta (di 20 anni) :-) – anatolyg

6

gcc fa tutti i tipi di ottimizzazioni, tra cui la selezione istruzioni basati su velocità di esecuzione del particolare CPU di essere ottimizzati per. Noterai che cose come x *= n sono spesso sostituite da un mix di SHL, ADD e/o SUB, specialmente quando n è una costante; mentre MUL viene usato solo quando il tempo medio di esecuzione (e cache/ecc. footprints) della combinazione di SHL-ADD-SUB supera quello di MUL, o n non è una costante (e quindi l'uso di loop con shl-add-sub sarebbe vieni più costoso).

In caso di argomenti della funzione: MOV può essere parallelizzato dall'hardware, mentre PUSH non può. (Il secondo PUSH deve attendere il completamento del primo PUSH a causa dell'aggiornamento del registro esp.) In caso di argomenti di funzione, i MOV possono essere eseguiti in parallelo.

+0

Qualche riferimento su questo tipo di ottimizzazioni? Grazie. – Tony

2

E 'su OS X per caso? Ho letto da qualche parte che richiede che il puntatore dello stack sia allineato ai limiti di 16 byte. Questo potrebbe spiegare questo tipo di generazione di codice.

ho trovato l'articolo: http://blogs.embarcadero.com/eboling/2009/05/20/5607

+1

Giusto per essere chiari, OS X ABI richiede solo che il puntatore dello stack sia allineato a 16 byte nel punto delle chiamate di funzione esterne. –

+0

Vedo, grazie per averlo indicato. Leggendo le altre risposte ora capisco che la generazione del codice movl è correlata a una programmazione migliorata. L'istruzione andl sembra essere lì solo per l'allineamento dello stack. –