2015-04-24 7 views
14

Sto provando a definire il tempo necessario per calcolare un sqrt utilizzando il seguente codice C semplice, dove readTSC() è una funzione per leggere il contatore di cicli della CPU.Perché il compilatore genera sqrts aggiuntivi nel codice assembly compilato

double sum = 0.0; 
int i; 
tm = readTSC(); 
for (i = 0; i < n; i++) 
    sum += sqrt((double) i); 
tm = readTSC() - tm; 
printf("%lld clocks in total\n",tm); 
printf("%15.6e\n",sum); 

Tuttavia, come ho stampato il codice assembly utilizzando

gcc -S timing.c -o timing.s 

su una macchina Intel, il risultato (vedi sotto) è stato sorprendente?

Perché nel codice assembly sono presenti due sqrts con uno che utilizza l'istruzione sqrtsd e l'altro con una chiamata di funzione? È correlato allo srotolamento del loop e sta tentando di eseguire due sqrts in un'unica iterazione?

E come capire la linea

ucomisd %xmm0, %xmm0 

Perché ci si confronta %xmm0 a se stesso?

//----------------start of for loop---------------- 
call readTSC 
movq %rax, -32(%rbp) 
movl $0, -4(%rbp) 
jmp .L4 
.L6: 
cvtsi2sd -4(%rbp), %xmm1 
// 1. use sqrtsd instruction 
sqrtsd %xmm1, %xmm0 
ucomisd %xmm0, %xmm0 
jp .L8 
je .L5 
.L8: 
movapd %xmm1, %xmm0 
// 2. use C funciton call 
call sqrt 
.L5: 
movsd -16(%rbp), %xmm1 
addsd %xmm1, %xmm0 
movsd %xmm0, -16(%rbp) 
addl $1, -4(%rbp) 
.L4: 
movl -4(%rbp), %eax 
cmpl -36(%rbp), %eax 
jl .L6 
//----------------end of for loop---------------- 
call readTSC 
+3

Questo deve essere un codice non ottimizzato. [Codice reale] (http://goo.gl/CewylI) disegna correttamente i rami (senza rami presi sul caso non NaN), e lascia fuori 'je' poiché sarà sempre vero dopo un' ucomisd '. –

risposta

23

Si sta utilizzando la libreria sqrt per la gestione degli errori. Vedere la documentazione di glibc: 20.5.4 Error Reporting by Mathematical Functions: le funzioni matematiche impostano errno per la compatibilità con sistemi che non dispongono di flag di eccezione IEEE754. Correlato: la pagina man di glibc math_error(7).

Come un'ottimizzazione, si cerca prima di eseguire la radice quadrata dal inline sqrtsd istruzioni, quindi controlla il risultato contro se stessa utilizzando l'istruzione ucomisd che stabilisce le bandiere come segue:

CASE (RESULT) OF 
    UNORDERED: ZF,PF,CF 111; 
    GREATER_THAN: ZF,PF,CF 000; 
    LESS_THAN: ZF,PF,CF 001; 
    EQUAL:  ZF,PF,CF 100; 
ESAC; 

In particolare, il confronto di uno QNaN a se stesso restituirà UNORDERED, che è ciò che otterrete se provate a prendere la radice quadrata di un numero negativo. Questo è coperto dal ramo jp. Il controllo je è solo paranoia, verificando l'uguaglianza esatta.


noti inoltre che gcc ha una -fno-math-errno option che sacrificare la movimentazione per la velocità di errore. Questa opzione fa parte di -ffast-math, ma può essere utilizzata da sola senza abilitare ottimizzazioni che cambiano i risultati.

sqrtsd produce correttamente NaN per ingressi negativi e NaN e imposta il flag IEEE754 non valido. Il controllo e la filiale sono solo per preservare la semantica di impostazione errno su cui la maggior parte del codice non si basa.

-fno-math-errno è l'impostazione predefinita su Darwin (OS X), in cui la libreria matematica non imposta mai errno, in modo che le funzioni possano essere sottolineate senza questo controllo.

+2

Nota che '-ffast-math 'fa molto più che sacrificare la gestione degli errori per la velocità. In particolare, infrange anche la conformità IEEE 754, cioè, usa con cura e solo se sai cosa stai facendo.Vedi anche http://stackoverflow.com/questions/7420665/what-does-gccs-ffast-math-actually-do – godfatherofpolka

+0

@godfatherofpolka sì, in generale. Tuttavia, in questo caso, è tutto ciò che fa. – Jester

+2

sì, è giusto, ho solo l'impressione che ogni menzione della bandiera del veloce-matematica debba contenere un'etichetta di avviso, ecco perché ho aggiunto quel commento. – godfatherofpolka