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.
Nel caso in cui causasse qualche confusione - probabilmente avrei dovuto ribattezzare la mia funzione "fizz" quando ho rimosso "|| i% 5" ;-) –