decido che voglio punto di riferimento una particolare funzione, così ho ingenuamente scrivere codice come questo:benchmarking, codice di riordino, volatile
#include <ctime>
#include <iostream>
int SlowCalculation(int input) { ... }
int main() {
std::cout << "Benchmark running..." << std::endl;
std::clock_t start = std::clock();
int answer = SlowCalculation(42);
std::clock_t stop = std::clock();
double delta = (stop - start) * 1.0/CLOCKS_PER_SEC;
std::cout << "Benchmark took " << delta << " seconds, and the answer was "
<< answer << '.' << std::endl;
return 0;
}
Un collega ha sottolineato che avrei dovuto dichiarare le variabili start
e stop
come volatile
a evitare il riordino del codice. Egli ha suggerito che l'ottimizzatore potrebbe, per esempio, in modo efficace riordinare il codice come questo:
std::clock_t start = std::clock();
std::clock_t stop = std::clock();
int answer = SlowCalculation(42);
All'inizio ero scettico che tale riordino estremo è stato permesso, ma dopo un po 'di ricerca e sperimentazione, ho imparato che era.
Ma volatile non si sentiva la soluzione giusta; non è volatile davvero solo per l'I/O mappato in memoria?
Tuttavia, ho aggiunto volatile
e ho scoperto che non solo il benchmark impiegava molto più tempo, ma era anche selvaggiamente incoerente dall'inizio alla fine. Senza volatilità (e avendo la fortuna di garantire che il codice non fosse riordinato), il benchmark richiedeva costantemente 600-700 ms. Con la volatilità, spesso ci sono voluti 1200 ms e talvolta più di 5000 ms. Gli elenchi di disassemblaggio delle due versioni non mostravano praticamente alcuna differenza se non una diversa selezione di registri. Questo mi fa chiedere se c'è un altro modo per evitare il riordino del codice che non ha effetti collaterali così travolgenti.
La mia domanda è:
Qual è il modo migliore per prevenire il codice di riordino nel codice di benchmarking come questo?
La mia domanda è simile a this one (che era sull'utilizzo di volatili per evitare di elisione invece di riordino), this one (che non ha risposto come prevenire riordino), e this one (che dibattuto se il problema è stato codice di riordino o eliminazione del codice morto). Mentre tutti e tre sono su questo argomento esatto, nessuno in realtà risponde alla mia domanda.
Aggiornamento: La risposta sembra essere che il mio collega si è sbagliato e che il riordino in questo modo non è coerente con lo standard. Ho svalutato tutti quelli che l'hanno detto e ho assegnato la generosità a Maxim.
Ho visto un caso (basato sul codice in this question) in cui Visual Studio 2010 ha riordinato le chiamate di clock come illustrato (solo in versioni a 64 bit). Sto cercando di creare un caso minimale per illustrarlo in modo da poter segnalare un errore su Microsoft Connect.
Per chi ha detto che volatili dovrebbero essere molto più lento, perché costringe legge e scrive a memoria, questo non è del tutto coerente con il codice viene emesso. Nella mia risposta su this question, mostro lo smontaggio per il codice con e senza volatile. All'interno del ciclo, tutto è tenuto in registri. Le uniche differenze significative sembrano essere la selezione del registro. Non capisco x86 assemblaggio abbastanza bene per sapere il motivo per cui le prestazioni della versione non-volatile è costantemente veloce mentre la versione volatile è incoerente (e a volte drammaticamente) più lento.
@juanchopanza: Cosa succede se il compilatore sa che 'SlowCalculation' non ha effetti collaterali? –
@OliCharlesworth buon punto. Devo pensare un po 'adesso. – juanchopanza
'volatile' significa semplicemente che l'accesso alla memoria potrebbe non essere ottimizzato e che non può essere riordinato rispetto ad altri effetti secondari osservabili del codice (compresi altri accessi volatili). Se 'SlowCalculation' non ha effetti collaterali, non sono sicuro che' volatile' lo renda "più sicuro". –