2012-01-26 15 views
12

Voglio scrivere una funzione template che fa qualcosa con un std::stack<T> e un'istanza di T, ad esempio:Un buon compilatore C++ ottimizzerà un riferimento?

template<class StackType> inline 
bool some_func(StackType const &s, typename StackType::value_type const &v) { 
    // ... 
} 

La ragione per cui mi passa v per riferimento è ovviamente quello di ottimizzare per il caso in cui StackType::value_type è un struct oppure class e non copiare un intero oggetto in base al valore.

Tuttavia, se StackType::value_type è un tipo "semplice" come int, è ovviamente meglio passarlo semplicemente in base al valore.

La domanda è: per un tipo come int che diventerebbe int const& come argomento formale nella funzione precedente, il compilatore ottimizzerà il riferimento e lo passerà semplicemente in base al valore?

+2

che sarebbe un cattivo compilatore. – lapk

+0

Se il compilatore decide effettivamente di incorporare la funzione, farà sicuramente la cosa più efficace, indipendentemente da come la definisci. – enobayram

risposta

6

Anche se non ho ancora testato alcun compilatore per questo, ne dubito. Supponete che passare un riferimento const sia indistinguibile dal semplice passaggio di un valore, ma ciò non è vero, e il compilatore non dovrebbe presumere che lo sia.

Poiché il riferimento è const, la funzione non può modificare il valore attraverso di esso, ma altre parti del codice potrebbero avere accesso alla variabile originale (non costante) e potrebbero modificarla. La tua funzione potrebbe chiamare qualche altra funzione che la modifica, per esempio, o potrebbe esserci un altro thread in esecuzione contemporaneamente che lo fa.

Se qualcos'altro modifica quella variabile originale, la funzione, con il suo riferimento, dovrebbe vedere il nuovo valore. Se il compilatore ha sostituito il riferimento con una copia, la funzione vedrebbe comunque il vecchio valore.

Tuttavia, potrebbe essere interessante la libreria call traits di Boost. Fornisce un modello tipo call_traits<T>::param_type che è un riferimento const per i tipi "grandi" che non si desidera copiare e un valore per i tipi "piccoli" in cui una copia sarebbe più efficiente. Fondamentalmente, quello che vuoi che il compilatore faccia implicitamente, puoi farlo esplicitamente con il tuo codice.

+3

Non hai ragione sui thread. L'ottimizzatore del compilatore spesso rompe il codice che funziona con strutture di dati "ingenui" (ingenui) prive di lock. Il compilatore ha bisogno di pensare ad altri thread! –

+0

Inoltre, le funzioni semplici non chiamano tutte le altre funzioni –

+0

@AlexandrPriymak, la domanda non specifica quale codice è nella funzione, quindi potrebbe essere una che chiama altre funzioni. E C++ 11 include un modello di memoria a conoscenza della concorrenza e funzionalità di threading standardizzate, quindi un compilatore C++ 11 deve considerare cosa potrebbero fare gli altri thread anche se un compilatore C++ 98 no. – Wyzard

6

Ogni volta che il compilatore è in grado di eseguire l'allineamento, l'ottimizzazione procedurale incrociata eliminerà le spese di ricerca e passaggio di un indirizzo.

Per le chiamate di funzione non in linea, il riferimento verrà probabilmente implementato come puntatore, non come valore pass-by.

3

Questo è MSVC2010 compilatore 64bit, ottimizzazioni completi:

int foo(int i) 
{ 
int a = i + i; 

return (a); 
} 
//Disassembly: 
//00000001`3ff11c50 8d0409   lea  eax,[rcx+rcx] 
//00000001`3ff11c53 c3    ret 

int bar(int const & i) 
{ 
int a = i + i; 

return (a); 
} 
//Disassembly: 
//00000001`3ff11c10 8b01   mov  eax,dword ptr [rcx] 
//00000001`3ff11c12 03c0   add  eax,eax 
//00000001`3ff11c14 c3    ret 

Nel primo caso, il valore viene passato RCX, nel secondo - indirizzo trasmesso RCX. Naturalmente, se le funzioni sono in linea, come ha detto Ben, il codice prodotto potrebbe essere assolutamente lo stesso in entrambi i casi.

Passare istanze di classi "piccole" (ad esempio, la classe ha solo un membro di dati int e un costruttore banale, un costruttore di copie bitwise ecc.) È più veloce in base al valore. Il compilatore lo ottimizzerà come, in sostanza, solo passando una copia di int. Quindi, puoi sovraccaricare la tua funzione, come ha sottolineato Wyzard, usando alcuni tratti tipografici.Qualcosa del genere:

template< typename PType > 
PType Func(typename std::enable_if< (sizeof(PType) > sizeof(size_t)), PType const & >::type f) 
{ 
//large things: reference 
} 

template< typename PType > 
PType Func(typename std::enable_if< (sizeof(PType) <= sizeof(size_t)), PType >::type f) 
{ 
//small things: value 
} 
+1

Si noti che l'analisi sperimentale è per le funzioni "normali", non per quelle basate su modelli. Questa è una differenza fondamentale; una funzione normale non può (in generale) avere la sua lista degli argomenti regolata dall'ottimizzazione del compilatore, mentre una funzione basata su un modello è necessariamente inlinabile e quindi può. –

+0

@BrooksMoses La funzione di inlining non dipende dal fatto che si tratti di una funzione basata su modelli o non basata su modelli. Semplicemente il fatto che uno abbia la definizione della funzione di un modello in ogni unità di compilazione in cui è utilizzato, non garantisce affatto la sua inclinazione ... Se le mie funzioni "sperimentali" sono dichiarate e definite simultaneamente in una unità di compilazione, saranno incorporate. E il codice sarà lo stesso. E viceversa, anche le funzioni basate su modelli "semplici" potrebbero non essere state sottolineate. – lapk

+0

Sì, ho semplificato eccessivamente. Tuttavia, se le funzioni "sperimentali" sono dichiarate e definite in una singola unità di compilazione, ma sono dichiarate in modo tale da essere esportate da tale unità funzionale, non possono essere inline. E non hai dato alcuna indicazione sul fatto che fossero statici o che stavi compilando un intero programma autonomo, il che significa che erano necessariamente esportati. –

6

mi guardo le opzioni di ottimizzazione gcc qui http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html

E in realtà c'è un'opzione per il vostro caso:

-fipa-sra

Eseguire scalare interprocedurale sostituzione di aggregati, rimozione di parametri inutilizzati e sostituzione di parametri passati per riferimento tramite parametri passati per valore.

abilitato a livelli -O2, -O3 e os

Per quanto ne so, -O2 è l'opzione usuale per build di rilascio su linux.

Quindi, la risposta breve è: uno dei buon compilatore fa

Problemi correlati