2016-05-05 14 views
10

Si consideri il seguente codice:Perché sqrt diventa molto più veloce senza -O2 in g ++ sul mio computer?

#include <cstdio> 
#include <cmath> 

const int COUNT = 1000000000; 

int main() 
{ 
    double sum = 0; 
    for (int i = 1; i <= COUNT; ++i) { 
     sum += sqrt(i); 
    } 
    printf("%f\n", sum); 
    return 0; 
} 

Senza -O2, funziona solo 2.9s sul mio computer, mentre si corre 6.4s con -O2.

Il mio computer è in Fedora 23, con g ++ 5.3.1.

Ho provato la stessa cosa su Ubuntu 14.04 (con g ++ 4.8), non ha il problema (tutti i 6.4).

+0

E come sei arrivato a questi risultati di temporizzazione - non c'è un meccanismo di temporizzazione nel tuo codice? – kfsone

+1

@kfsone Presumibilmente con il comando 'time'. Non è necessario creare il proprio meccanismo di temporizzazione se uno perfettamente buono è già disponibile. – hvd

+0

Ottengo risultati simili ....!?! (anche con GCC 6.1.0) – Galik

risposta

3

La versione Naive utilizza la chiamata alla funzione glibc sqrt.

La versione ottimizzata utilizza l'istruzione SSE sqrtsd. Ma dopo che l'istruzione è stata completata, controlla che il valore del risultato non sia un NaN. Se il valore del risultato è NaN, chiama la funzione glibc sqrt per impostare i flag di errore corretti (vedere la pagina di manuale per math_error(7)). Vedi Why does compiler generate additional sqrts in the compiled assembly code per una spiegazione dettagliata.

Perché gcc pensa che questo sia più veloce? Nessuno sa. Se si è certi che i propri numeri non generano NaN, utilizzare l'opzione di compilazione -fno-math-errno.

+1

Con '-ffast-math', questo controllo viene rimosso e genera un codice più veloce. –

+1

Tranne che a '-O', GCC usa l'istruzione' sqrtsd' (con diversi operandi), ma ottiene prestazioni migliori rispetto a '-O2'. – hvd

+0

@hvd, hai ragione. Sembra che la previsione dei rami sia in qualche modo coinvolta? '-O2' genera' jp 'e' -O' usa 'jnp '. – gudok

0

L'analisi dell'assembly può generare alcune risposte, ma il modo più semplice per vedere la differenza di codice è fare -fdump-tree-optimized. Il problema sembra essere correlato agli overload di sqrt, ovvero quello fornito dalla libreria C sqrt(double) e C++ 11 sqrt(int). Quest'ultimo sembra essere più veloce, e GCC non sembra preoccuparsi se si utilizza -std=c++11 o il prefisso std:: a sqrt oppure no.

Ecco un estratto per la discarica con -O2 o -O (-O con nessun numero consente ottimizzazioni, per disabilitare tutte le ottimizzazioni, omettere -O):

int i; 
    double sum; 
    double _9; 
    __type _10; 

    <bb 2>: 

    <bb 3>: 
    # sum_15 = PHI <sum_6(3), 0.0(2)> 
    # i_16 = PHI <i_7(3), 1(2)> 
    _9 = (double) i_16; 
    _10 = __builtin_sqrt (_9); 
    sum_6 = _10 + sum_15; 
    i_7 = i_16 + 1; 
    if (i_7 == 1000000001) 
    goto <bb 4>; 
    else 
    goto <bb 3>; 

Poi, senza -O2:

<bb 4>: 
    _8 = std::sqrt<int> (i_2); 
    sum_9 = sum_1 + _8; 
    i_10 = i_2 + 1; 
    goto <bb 3>; 

Avviso utilizza std::sqrt<int>. Per gli scettici, vedi Why sqrt in global scope is much slower than std::sqrt in MinGW?