2015-02-24 7 views
7

Sto cercando di conoscere la vettorizzazione studiando codice C semplice compilato in gcc con ottimizzazione -O3. Più in particolare, quanto bene i compilatori vettorializzano. È un viaggio personale verso la possibilità di verificare le prestazioni di gcc -O3 con un calcolo più complesso. Capisco che la saggezza convenzionale è che i compilatori sono migliori delle persone, ma non ho mai dato per scontato tale saggezza.Ridondanza codice assembly in codice C ottimizzato

Nel mio primo semplice test, però, sto trovando alcune delle scelte gcc rende piuttosto strano e, abbastanza onestamente, grossolanamente negligente in termini di ottimizzazione. Sono disposto ad assumere che ci sia qualcosa che il compilatore è intenzionale e conosce qualcosa sulla CPU (Intel i5-2557M in questo caso) che io non conosco. Ma ho bisogno di una conferma da parte di persone esperte.

mio semplice codice di prova (segmento) è:

int i; 
float a[100]; 

for (i=0;i<100;i++) a[i]= (float) i*i; 

Il codice assieme risultante (segmento) corrispondente al ciclo for è il seguente:

.L6:      ; loop starts here 
    movdqa xmm0, xmm1  ; copy packed integers in xmm1 to xmm0 
.L3: 
    movdqa xmm1, xmm0  ; wait, what!? WHY!? this is redundant. 
    cvtdq2ps xmm0, xmm0 ; convert integers to float 
    add rax, 16    ; increment memory pointer for next iteration 
    mulps xmm0, xmm0  ; pack square all integers in xmm0 
    paddd xmm1, xmm2  ; pack increment all integers by 4 
    movaps XMMWORD PTR [rax-16], xmm0 ; store result 
    cmp rax, rdx   ; test loop termination 
    jne .L6     

comprendo tutti i passaggi e, a livello computazionale, tutto ha senso. Quello che non capisco, però, è gcc scegliendo di inserire nel ciclo iterativo un passo per caricare XMM1 con xmm0 subito dopo xmm0 stato caricato con XMM1. Ad esempio

.L6 
     movdqa xmm0, xmm1  ; loop starts here 
.L3 
     movdqa xmm1, xmm0  ; grrr! 

Questo solo mi mette in dubbio la sanità mentale dell'ottimizzatore. Ovviamente, il MOVDQA in più non disturba i dati, ma a valore facciale, sembrerebbe gravemente negligente da parte di gcc.

precedenza nel codice assembly (non mostrato), xmm0 e XMM2 sono inizializzate ad un valore significativo per vettorizzazione, quindi ovviamente, all'inizio del ciclo, il codice deve saltare la prima MOVDQA. Ma perché lo gcc non viene semplicemente riorganizzato, come mostrato di seguito.

.L3 
     movdqa xmm1, xmm0  ; initialize xmm1 PRIOR to loop 
.L6 
     movdqa xmm0, xmm1  ; loop starts here 

O, meglio ancora, semplicemente inizializzare XMM1 invece di xmm0 e il dump del MOVDQA XMM1, xmm0 passo del tutto!

Sono pronto a credere che la CPU è abbastanza intelligente per ignorare il passaggio ridondante o qualcosa del genere, ma come posso fidarmi gcc per ottimizzare al meglio codice complesso, se si può anche ottenere questo semplice codice giusto? Oppure qualcuno può fornire una spiegazione valida che mi desse fede che lo gcc -O3 è roba buona?

+0

@Down votanti: si prega di commentare perché. – Stefan

+0

Hai compilato con le ottimizzazioni attivate. Su alcuni livelli di ottimizzazione, l'operazione di spostamento ridondante viene eliminata. –

+1

Sei sicuro che il tuo codice sia più veloce dei compilatori? Hai provato a crearli? – Degustaf

risposta

4

sono sicuro al 100%, ma sembra che il loop distrugge xmm0 convertendola in float, in modo da avere il valore intero in xmm1 e quindi copiare su un altro registro (in questo caso xmm0).

Sebbene i compilatori siano a volte in grado di impartire istruzioni non necessarie, non riesco davvero a vedere come questo sia il caso in questo caso.

Se volete xmm0 (o xmm1) di rimanere intero, quindi non hanno un cast di float per il primo valore della i. Forse ciò che si voleva fare è:

for (i=0;i<100;i++) 
    a[i]= (float)(i*i); 

Ma d'altra parte, 4.9.2 gcc non sembra di fare questo:

g++ -S -O3 floop.cpp 

.L2: 
    cvtdq2ps %xmm1, %xmm0 
    mulps %xmm0, %xmm0 
    addq $16, %rax 
    paddd %xmm2, %xmm1 
    movaps %xmm0, -16(%rax) 
    cmpq %rbp, %rax 
    jne .L2 

né Clang (3.7.0 da circa 3 settimane fa)

clang++ -S -O3 floop.cpp 


    movdqa .LCPI0_0(%rip), %xmm0 # xmm0 = [0,1,2,3] 
    xorl %eax, %eax 
    .align 16, 0x90 
.LBB0_1:        # %vector.body 
             # =>This Inner Loop Header: Depth=1 
    movd %eax, %xmm1 
    pshufd $0, %xmm1, %xmm1  # xmm1 = xmm1[0,0,0,0] 
    paddd %xmm0, %xmm1 
    cvtdq2ps %xmm1, %xmm1 
    mulps %xmm1, %xmm1 
    movaps %xmm1, (%rsp,%rax,4) 
    addq $4, %rax 
    cmpq $100, %rax 
    jne .LBB0_1 

di codice che ho compilato:

extern int printf(const char *, ...); 

int main() 
{ 
    int i; 
    float a[100]; 

    for (i=0;i<100;i++) 
     a[i]= (float) i*i; 

    for (i=0; i < 100; i++) 
     printf("%f\n", a[i]); 
} 

(Ho aggiunto il printf per evitare che il compilatore si sbarazzasse di TUTTO il codice)

+0

Ma questo è ciò che accade realmente. Se guardi all'assemblaggio che puoi vedere, xmm0 viene convertito in float, quadrato e salvato. La domanda è, perché il compilatore sovrascrive xmm1 dopo il loop jump. – Marandil

+0

Ah, buon punto. Quindi, è solo un altro caso di "scrivere compilatori è difficile".Se hai voglia di una sfida, direi che puoi provare a scoprire dove accade in gcc e proporre una soluzione. –

+1

O semplicemente l'aggiornamento a un gcc più recente, forse? –