2009-07-13 6 views
9

Ecco un po 'di codice (programma completo segue più avanti nella questione):Ottimizzazione della divisione in gcc

template <typename T> 
T fizzbuzz(T n) { 
    T count(0); 
    #if CONST 
     const T div(3); 
    #else 
     T div(3); 
    #endif 
    for (T i(0); i <= n; ++i) { 
     if (i % div == T(0)) count += i; 
    } 
    return count; 
} 

Ora, se io chiamo questa funzione modello con int, tanto sono un fattore di differenza 6 prestazioni in base alle se definisco cONST o meno:

$ gcc --version 
gcc (GCC) 3.4.4 (cygming special, gdc 0.12, using dmd 0.125) 

$ make -B wrappedint CPPFLAGS="-O3 -Wall -Werror -DWRAP=0 -DCONST=0" && 
time ./wrappedint 
g++ -O3 -Wall -Werror -DWRAP=0 -DCONST=0 wrappedint.cpp -o wrappedi 
nt 
484573652 

real 0m2.543s 
user 0m2.059s 
sys  0m0.046s 

$ make -B wrappedint CPPFLAGS="-O3 -Wall -Werror -DWRAP=0 -DCONST=1" && 
time ./wrappedint 
g++ -O3 -Wall -Werror -DWRAP=0 -DCONST=1 wrappedint.cpp -o wrappedi 
nt 
484573652 

real 0m0.655s 
user 0m0.327s 
sys  0m0.046s 

Esaminando lo smontaggio risulta che, nel caso veloce (const), il modulo è stato trasformato in una cosa moltiplicazione e spostamento tipo, mentre nel caso lenta (non-const) sta usando idivl.

Ancora peggio, se provo a racchiudere il mio intero in una classe, l'ottimizzazione non avviene se utilizzo const o no. Il codice utilizza sempre idivl e scorre lenta:

#include <iostream> 

struct WrappedInt { 
    int v; 
    explicit WrappedInt(const int &val) : v(val) {} 
    bool operator<=(const WrappedInt &rhs) const { return v <= rhs.v; } 
    bool operator==(const WrappedInt &rhs) const { return v == rhs.v; } 
    WrappedInt &operator++() { ++v; return *this; } 
    WrappedInt &operator+=(const WrappedInt &rhs) { v += rhs.v; return *this; } 
    WrappedInt operator%(const WrappedInt &rhs) const 
     { return WrappedInt(v%rhs.v); } 
}; 

std::ostream &operator<<(std::ostream &s, WrappedInt w) { 
    return s << w.v; 
} 

template <typename T> 
T fizzbuzz(T n) { 
    T count(0); 
    #if CONST 
     const T div(3); 
    #else 
     T div(3); 
    #endif 
    for (T i(0); i <= n; ++i) { 
     if (i % div == T(0)) count += i; 
    } 
    return count; 
} 

int main() { 
    #if WRAP 
     WrappedInt w(123456789); 
     std::cout << fizzbuzz(w) << "\n"; 
    #else 
     std::cout << fizzbuzz<int>(123456789) << "\n"; 
    #endif 
} 

Le mie domande sono:

1) Esiste un semplice principio di C++ per sé, o di ottimizzazione di gcc, il che spiega il motivo per cui questo accade, o è solo un caso di "varie corse euristiche, questo è il codice che ottieni"?

2) Esiste un modo per far sì che il compilatore si renda conto che il mio const WrappedInt localmente dichiarato e mai referenziato può essere trattato come un valore const di compilazione? Voglio che questa cosa sia una sostituzione diretta per int nei template.

3) Esiste un modo noto di eseguire il wrapping di un int tale che il compilatore possa scartare il wrapping durante l'ottimizzazione? L'obiettivo è che WrappedInt sarà un modello basato su criteri. Ma se una politica del "do-nothing" si traduce in penalità di velocità 6x essenzialmente arbitrarie rispetto a int, io sto meglio a svantaggiare questa situazione e usare direttamente int.

+0

Nel caso in cui causasse qualche confusione - probabilmente avrei dovuto ribattezzare la mia funzione "fizz" quando ho rimosso "|| i% 5" ;-) –

risposta

6

Sto indovinando la sua versione GCC severamente vecchia che stai utilizzando. Il più vecchio compilatore che ho sulla mia macchina - gcc-4.1.2, esegue il modo veloce con entrambe le versioni non-const e wrap (e lo fa solo a -O1).

+0

Questa è una buona notizia, grazie. –

+0

Qualche ragione particolare per cui non sei aggiornato in 4 anni? –

+0

Per impostazione predefinita Cygwin è 3.4. Presumo che lo stiano ancora usando per costruire cygwin stesso, o qualcosa del genere. Ha un pacchetto gcc-4, quindi, quindi ci proverò. Meno male dei 12 mesi + che la versione di cygwin di gnupg conteneva un difetto critico di sicurezza risolto a monte. –

1

Provare a combinare const int v nella classe WrappedInt con const T nella funzione fizzbuzz e verificare se il compilatore può ottimizzarlo.

Dichiarando const int hai creato un caso speciale - una costante di tempo di compilazione. Il compilatore sa qual è il valore e può ottimizzarlo più pesantemente di un valore che potrebbe cambiare durante l'esecuzione del programma.

+0

Sfortunatamente 'WrappedInt' ha due operatori che modificano' v'. Ma introducendo una nuova classe, 'WrappedConstInt' con loro rimossi, aggiungendo un appropriato' operatore% ', e facendo' div' a 'const WrappedConstInt', non attiva l'ottimizzazione. Funziona lentamente. –

+0

E capisco che "const int" è speciale. Tuttavia, anche quando non-const, il compilatore sta tirando il valore in un registro e il registro non viene mai modificato nel ciclo. Quindi speravo piuttosto che (a) alcuni DFA permettessero di vedere che un int locale che non è mai modificato e nessun riferimento preso, è buono come const, e gli dà il trattamento, e/o (b) che un const struct contenente un int è proprio come const come 'const int'. –

+0

Quindi immagino che la domanda diventi "c'è una forte ragione per cui né (a) né (b) accade, o è solo che gcc non lo gestisce?" –

0

Esiste un modo noto di eseguire il wrapping di un int tale che il compilatore possa scartare il wrapping durante l'ottimizzazione?

Provare a passare WrappedInt in base al valore. Quindi è possibile passare WrappedInt s nei registri. Pass-by-const-reference a volte costringe gcc a ricadere nello stack per il passaggio degli argomenti.

Informazioni sul rallentamento int vs const int, posso solo ipotizzare che GCC stia cercando di giocare sul sicuro di fronte all'aliasing. Fondamentalmente, se non può dimostrare che div non è un alias per un'altra variabile più accessibile, non può trasformarla in una costante. Se lo dichiari const, GCC presume che non sia alias e compia la conversione in una costante. A parte lo idivl, dovresti anche vedere un recupero di memoria, una volta, quando inserisci la funzione, al contrario dei valori immediati usati per il caso const.

+0

In entrambi i casi wra e non-const int, il div è inizializzato con "' movl $ 3,% edi' "e non tocca mai la memoria. E la modifica di tutti gli operatori di WrappedInt (e del costruttore) da passare per valore non abilita l'ottimizzazione. –

0

La differenza di velocità è dovuta al fatto che il compilatore non sa se "div" cambierà valore. Quando è non-const, lo considera come una variabile passata. Potrebbe essere qualsiasi cosa, e quindi il compilatore userà un'istruzione che divide due variabili - idivl. Quando si dice che è const, il compilatore è libero di trattare esattamente come se avessi scelto:

 
if (i % 3 == 0) 

Sono un po 'sorpreso che non ha utilizzato AND bit a bit (&).

WrappedInt non viene ottimizzato perché, beh, non è un int. È una classe.

Qualcosa che potresti fare è incorporare fizzbuzz in WrappedInt.

+1

Solo il modulo dei poteri di due può essere espresso usando AND. –