2012-12-19 5 views
77

Così, ho avuto questo codice:Perché l'aggiunta di commenti all'assieme causa un cambiamento così radicale nel codice generato?

constexpr unsigned N = 1000; 
void f1(char* sum, char* a, char* b) { 
    for(int i = 0; i < N; ++i) { 
     sum[i] = a[i] + b[i]; 
    } 
} 

void f2(char* sum, char* a, char* b) { 
    char* end = sum + N; 
    while(sum != end) { 
     *sum++ = *a++ + *b++; 
    } 
} 

Volevo vedere il codice che GCC 4.7.2 genererebbe. Così mi sono imbattuto g++ -march=native -O3 -masm=intel -S a.c++ -std=c++11 ed ho ottenuto il seguente risultato:

 .file "a.c++" 
     .intel_syntax noprefix 
     .text 
     .p2align 4,,15 
     .globl _Z2f1PcS_S_ 
     .type _Z2f1PcS_S_, @function 
_Z2f1PcS_S_: 
.LFB0: 
     .cfi_startproc 
     lea  rcx, [rdx+16] 
     lea  rax, [rdi+16] 
     cmp  rdi, rcx 
     setae r8b 
     cmp  rdx, rax 
     setae cl 
     or  cl, r8b 
     je  .L5 
     lea  rcx, [rsi+16] 
     cmp  rdi, rcx 
     setae cl 
     cmp  rsi, rax 
     setae al 
     or  cl, al 
     je  .L5 
     xor  eax, eax 
     .p2align 4,,10 
     .p2align 3 
.L3: 
     movdqu xmm0, XMMWORD PTR [rdx+rax] 
     movdqu xmm1, XMMWORD PTR [rsi+rax] 
     paddb xmm0, xmm1 
     movdqu XMMWORD PTR [rdi+rax], xmm0 
     add  rax, 16 
     cmp  rax, 992 
     jne  .L3 
     mov  ax, 8 
     mov  r9d, 992 
.L2: 
     sub  eax, 1 
     lea  rcx, [rdx+r9] 
     add  rdi, r9 
     lea  r8, [rax+1] 
     add  rsi, r9 
     xor  eax, eax 
     .p2align 4,,10 
     .p2align 3 
.L4: 
     movzx edx, BYTE PTR [rcx+rax] 
     add  dl, BYTE PTR [rsi+rax] 
     mov  BYTE PTR [rdi+rax], dl 
     add  rax, 1 
     cmp  rax, r8 
     jne  .L4 
     rep 
     ret 
.L5: 
     mov  eax, 1000 
     xor  r9d, r9d 
     jmp  .L2 
     .cfi_endproc 
.LFE0: 
     .size _Z2f1PcS_S_, .-_Z2f1PcS_S_ 
     .p2align 4,,15 
     .globl _Z2f2PcS_S_ 
     .type _Z2f2PcS_S_, @function 
_Z2f2PcS_S_: 
.LFB1: 
     .cfi_startproc 
     lea  rcx, [rdx+16] 
     lea  rax, [rdi+16] 
     cmp  rdi, rcx 
     setae r8b 
     cmp  rdx, rax 
     setae cl 
     or  cl, r8b 
     je  .L19 
     lea  rcx, [rsi+16] 
     cmp  rdi, rcx 
     setae cl 
     cmp  rsi, rax 
     setae al 
     or  cl, al 
     je  .L19 
     xor  eax, eax 
     .p2align 4,,10 
     .p2align 3 
.L17: 
     movdqu xmm0, XMMWORD PTR [rdx+rax] 
     movdqu xmm1, XMMWORD PTR [rsi+rax] 
     paddb xmm0, xmm1 
     movdqu XMMWORD PTR [rdi+rax], xmm0 
     add  rax, 16 
     cmp  rax, 992 
     jne  .L17 
     add  rdi, 992 
     add  rsi, 992 
     add  rdx, 992 
     mov  r8d, 8 
.L16: 
     xor  eax, eax 
     .p2align 4,,10 
     .p2align 3 
.L18: 
     movzx ecx, BYTE PTR [rdx+rax] 
     add  cl, BYTE PTR [rsi+rax] 
     mov  BYTE PTR [rdi+rax], cl 
     add  rax, 1 
     cmp  rax, r8 
     jne  .L18 
     rep 
     ret 
.L19: 
     mov  r8d, 1000 
     jmp  .L16 
     .cfi_endproc 
.LFE1: 
     .size _Z2f2PcS_S_, .-_Z2f2PcS_S_ 
     .ident "GCC: (GNU) 4.7.2" 
     .section  .note.GNU-stack,"",@progbits 

faccio schifo a leggere di assemblaggio, così ho deciso di aggiungere alcuni marcatori di sapere dove i corpi dei cicli sono andati:

constexpr unsigned N = 1000; 
void f1(char* sum, char* a, char* b) { 
    for(int i = 0; i < N; ++i) { 
     asm("# im in ur loop"); 
     sum[i] = a[i] + b[i]; 
    } 
} 

void f2(char* sum, char* a, char* b) { 
    char* end = sum + N; 
    while(sum != end) { 
     asm("# im in ur loop"); 
     *sum++ = *a++ + *b++; 
    } 
} 

e GCC battibecco questo fuori:

.file "a.c++" 
    .intel_syntax noprefix 
    .text 
    .p2align 4,,15 
    .globl _Z2f1PcS_S_ 
    .type _Z2f1PcS_S_, @function 
_Z2f1PcS_S_: 
.LFB0: 
    .cfi_startproc 
    xor eax, eax 
    .p2align 4,,10 
    .p2align 3 
.L2: 
#APP 
# 4 "a.c++" 1 
    # im in ur loop 
# 0 "" 2 
#NO_APP 
    movzx ecx, BYTE PTR [rdx+rax] 
    add cl, BYTE PTR [rsi+rax] 
    mov BYTE PTR [rdi+rax], cl 
    add rax, 1 
    cmp rax, 1000 
    jne .L2 
    rep 
    ret 
    .cfi_endproc 
.LFE0: 
    .size _Z2f1PcS_S_, .-_Z2f1PcS_S_ 
    .p2align 4,,15 
    .globl _Z2f2PcS_S_ 
    .type _Z2f2PcS_S_, @function 
_Z2f2PcS_S_: 
.LFB1: 
    .cfi_startproc 
    xor eax, eax 
    .p2align 4,,10 
    .p2align 3 
.L6: 
#APP 
# 12 "a.c++" 1 
    # im in ur loop 
# 0 "" 2 
#NO_APP 
    movzx ecx, BYTE PTR [rdx+rax] 
    add cl, BYTE PTR [rsi+rax] 
    mov BYTE PTR [rdi+rax], cl 
    add rax, 1 
    cmp rax, 1000 
    jne .L6 
    rep 
    ret 
    .cfi_endproc 
.LFE1: 
    .size _Z2f2PcS_S_, .-_Z2f2PcS_S_ 
    .ident "GCC: (GNU) 4.7.2" 
    .section .note.GNU-stack,"",@progbits 

Questo è notevolmente più breve e presenta alcune differenze significative come la mancanza di istruzioni SIMD. Mi aspettavo la stessa uscita, con alcuni commenti da qualche parte nel mezzo. Sto facendo qualche ipotesi sbagliata qui? L'ottimizzatore di GCC è ostacolato dai commenti di ASM?

+25

Mi aspetto che GCC (e la maggior parte dei compilatori) tratti il ​​blocco ASM come blocchi. Quindi non possono ragionare su ciò che accade attraverso una tale scatola. E questo inibisce molte ottimizzazioni, specialmente quelle che vengono trasportate attraverso i confini del loop. –

+9

Prova il modulo 'asm' esteso con gli elenchi di output e clobber vuoti. –

+0

@KerrekSB che aspetto ha? :) –

risposta

57

Le interazioni con le ottimizzazioni sono spiegate a metà della pagina "Assembler Instructions with C Expression Operands" nella documentazione.

GCC non tenta di comprendere alcuno dell'assemblaggio effettivo all'interno di asm; l'unica cosa che sa del contenuto è ciò che tu (facoltativamente) dici nelle specifiche dell'operando di output e di input e nell'elenco dei clobber dei registri.

In particolare, nota:

Un'istruzione asm senza operandi di uscita saranno trattati in modo identico ad un asm istruzione volatile.

e

Il volatile parola chiave indica che l'istruzione ha importanti effetti collaterali [...]

Così la presenza del asm all'interno del vostro ciclo è inibito un'ottimizzazione vettorizzazione , poiché GCC presume che abbia effetti collaterali.

21

Si noti che gcc ha vettorializzato il codice, dividendo il corpo del ciclo in due parti, la prima elaborando 16 elementi alla volta e il secondo facendo il resto in seguito.

Come commentato da Ira, il compilatore non analizza il blocco asm, quindi non sa che è solo un commento. Anche se così fosse, non ha modo di sapere cosa intendi. I cicli ottimizzati hanno il corpo raddoppiato, dovrebbe mettere il tuo asm in ciascuno? Ti piacerebbe che non fosse eseguito 1000 volte? Non lo sa, percorre la strada sicura e ritorna al semplice loop singolo.

4

Non sono d'accordo con "gcc non capisce cosa c'è nel blocco asm()". Ad esempio, gcc può gestire abbastanza bene l'ottimizzazione dei parametri e persino riorganizzare i blocchi asm() in modo che si mescoli con il codice C generato. Questo è il motivo per cui, se si guarda l'assemblatore inline, ad esempio il kernel di Linux, è quasi sempre prefisso con __volatile__ per assicurare che il compilatore "non sposti il ​​codice". Ho gcc spostato il mio "rdtsc" in giro, il che ha reso le mie misurazioni del tempo necessario per fare certe cose.

Come documentato, gcc considera determinati tipi di blocchi asm() come "speciali" e pertanto non ottimizza il codice su entrambi i lati del blocco.

Questo non vuol dire che gcc a volte non viene confuso dai blocchi assembler inline, o semplicemente decide di rinunciare a qualche particolare ottimizzazione perché non può seguire le conseguenze del codice assembler, ecc, ecc. Ancora più importante, spesso può confondersi con i tag di clobber mancanti, quindi se hai qualche istruzione come cpuid che modifica il valore di EAX-EDX, ma hai scritto il codice in modo che utilizzi solo EAX, il compilatore può memorizzare cose in EBX , ECX ed EDX, e quindi il tuo codice si comporta in modo molto strano quando questi registri vengono sovrascritti ... Se sei fortunato, si blocca immediatamente - quindi è facile capire cosa succede. Ma se sei sfortunato, va in crash lungo la linea ... Un'altra difficile è l'istruzione dividere che dà un secondo risultato in edx. Se non ti interessa il modulo, è facile dimenticare che EDX è stato modificato.

+1

gcc in realtà non capisce cosa c'è nel blocco asm - devi dirlo tramite un'istruzione estesa asm. senza questa informazione extra, gcc non si muoverà attorno a tali blocchi. gcc inoltre non si confonde nei casi in cui affermi: hai semplicemente commesso un errore di programmazione dicendo a gcc che può usare quei registri quando in realtà il tuo codice li nasconde. –

+0

Risposta tardiva, ma penso che valga la pena di dirlo. 'volatile asm' dice a GCC che il codice potrebbe avere" effetti collaterali importanti "e lo tratterà con maggiore attenzione.Può essere cancellato come parte dell'ottimizzazione del codice morto o spostato all'esterno. L'interazione con il codice C deve assumere questo caso (raro) e imporre una rigorosa valutazione sequenziale (ad es. Creando dipendenze all'interno di asm). – edmz

-2

Ogni commento di assembly funge da punto di interruzione. È possibile eseguire il programma in un interprete che si interrompe a ogni commento e stampa lo stato di ogni variabile (utilizzando le informazioni di debug). Questi punti devono esistere in modo da osservare l'ambiente (stato dei registri e memoria).

Senza il commento, non esiste alcun punto di osservazione e il ciclo viene compilato come una singola funzione matematica che acquisisce un ambiente e produce un ambiente modificato.

Si desidera conoscere la risposta a una domanda senza senso: si desidera sapere come viene compilata ogni istruzione (o forse blocco o intervallo di istruzioni), ma non viene compilata alcuna istruzione isolata (o blocco); l'intera roba è compilata nel suo complesso.

Una domanda migliore sarebbe:

Ciao GCC. Perché credi che questo output di ASM stia implementando il codice sorgente? Per favore spiega passo dopo passo, con ogni assunzione.

Ma allora non si vorrebbe leggere una prova più lunga dell'uscita ASM, scritta in termini di rappresentazione interna GCC.

+1

* Questi punti devono esistere in modo da osservare l'ambiente (stato dei registri e della memoria). * - questo potrebbe essere vero per il codice non ottimizzato. Con le ottimizzazioni abilitate, tutte le funzioni potrebbero scomparire dal binario. Stiamo parlando di codice ottimizzato qui. –

+0

@BartekBanachewicz No, devono ancora esistere per definizione. "Stiamo parlando di codice ottimizzato qui_" No, stiamo parlando di "asm". – curiousguy

+1

Stiamo parlando dell'assemblaggio generato come risultato della compilazione con ottimizzazioni abilitate. Quindi sei ** sbagliato ** nell'affermare che qualcosa deve esistere. –

Problemi correlati