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?
@Down votanti: si prega di commentare perché. – Stefan
Hai compilato con le ottimizzazioni attivate. Su alcuni livelli di ottimizzazione, l'operazione di spostamento ridondante viene eliminata. –
Sei sicuro che il tuo codice sia più veloce dei compilatori? Hai provato a crearli? – Degustaf