2016-07-07 22 views
18

Chandler Carruth ha introdotto due funzioni nel suo CppCon2015 talk che possono essere utilizzate per eseguire un'inibizione a grana fine dell'ottimizzatore. Sono utili per scrivere dei micro-benchmark che l'ottimizzatore non metterà semplicemente a bada in assenza di significato.Barriera di ottimizzazione per i microbenchmarks in MSVC: indicare all'ottimizzatore che memoria del clobber?

void clobber() { 
    asm volatile("" : : : "memory"); 
} 

void escape(void* p) { 
    asm volatile("" : : "g"(p) : "memory"); 
}  

Queste istruzioni di assembly in linea consentono di modificare le ipotesi dell'ottimizzatore.

La dichiarazione di assemblaggio in clobber indica che il codice assembly in esso può leggere e scrivere in qualsiasi punto della memoria. Il codice assembly effettivo è vuoto, ma l'ottimizzatore non lo esaminerà perché è asm volatile. Ci crede quando diciamo che il codice potrebbe leggere e scrivere ovunque nella memoria. Ciò impedisce efficacemente all'ottimizzatore di riordinare o eliminare le scritture di memoria prima della chiamata a clobber e impone la lettura della memoria dopo la chiamata a clobber & dagger ;.

Quello in escape rende inoltre visibile il puntatore p al blocco di assiemi. Anche in questo caso, poiché l'ottimizzatore non esaminerà il codice di assembly inline effettivo, il codice può essere vuoto e l'ottimizzatore continuerà a ritenere che il blocco utilizzi l'indirizzo puntato dal puntatore p. Ciò forza efficacemente qualsiasi punto p in memoria e non in un registro, poiché il blocco di assiemi potrebbe eseguire una lettura da tale indirizzo.

(Questo è importante perché la funzione clobber non imporrà le letture né le scritture per qualsiasi cosa che i compilatori decidano di inserire in un registro, poiché l'istruzione di assemblaggio in clobber non specifica che qualcosa in particolare deve essere visibile al assemblaggio.)

Tutto questo accade senza alcun codice aggiuntivo generato direttamente da queste "barriere". Sono artefatti puramente in fase di compilazione.

Queste estensioni di linguaggio di utilizzo supportate in GCC e in Clang, però. C'è un modo per avere un comportamento simile quando si usa MSVC?


e pugnale; Per capire perché l'ottimizzatore deve pensare in questo modo, immagina se il blocco di assiemi fosse un ciclo che aggiunge 1 a ogni byte in memoria.

+1

Sembra [come] (http://stackoverflow.com/ a/8845503/786653) '_ReadWriteBarrier' potrebbe essere la risposta per' clobber'. Però non so di "fuga". Forse '_ReadWriteBarrier' oltre a distribuire il puntatore ad una funzione definita esternamente. – user786653

+0

Oh, ho dimenticato di menzionare un'altra caratteristica di questi: non generano alcun codice. Qualsiasi effetto scompare dopo aver completato l'ottimizzatore. Niente rimane fino al runtime. Sono semplicemente in fase di compilazione. –

+0

Come @ utente786653 ha detto, '_ReadWriteBarrier' (o forse solo' _ReadBarrier'/'_WriteBarrier' se è tutto ciò che serve) avrà lo stesso effetto in MSVC come' clobber'. Per 'escape', la mia esperienza nell'analisi dell'output di assembly è che MSVC farà la cosa giusta se si contrassegna la variabile' volatile'. Naturalmente, vi è un sovraccarico di runtime in quanto, poiché il codice generato * * * manterrà la variabile aggiornata in memoria. Non è una soluzione perfetta, ma non ho trovato niente di meglio. –

risposta

1

Ho utilizzato quanto segue al posto di escape.

#ifdef _MSC_VER 
#pragma optimize("", off) 
template <typename T> 
inline void escape(T* p) { 
    *reinterpret_cast<char volatile*>(p) = 
     *reinterpret_cast<char const volatile*>(p); // thanks, @milleniumbug 
} 
#pragma optimize("", on) 
#endif 

Non è perfetto ma è abbastanza vicino, credo.

Purtroppo, non ho un modo di emulare clobber.

6

Dato your approximation of escape(), si dovrebbe anche andare bene con la seguente approssimazione di clobber() (notare che questo è un progetto di idea, rinviando parte della soluzione per l'implementazione della funzione nextLocationToClobber()):

// always returns false, but in an undeducible way 
bool isClobberingEnabled(); 

// The challenge is to implement this function in a way, 
// that will make even the smartest optimizer believe that 
// it can deliver a valid pointer pointing anywhere in the heap, 
// stack or the static memory. 
volatile char* nextLocationToClobber(); 

const bool clobberingIsEnabled = isClobberingEnabled(); 
volatile char* clobberingPtr; 

inline void clobber() { 
    if (clobberingIsEnabled) { 
     // This will never be executed, but the compiler 
     // cannot know about it. 
     clobberingPtr = nextLocationToClobber(); 
     *clobberingPtr = *clobberingPtr; 
    } 
} 

UPDATE

domanda: Come garantire che isClobberingEnabled restituisce false "in modo indefinibile"? Certamente sarebbe banale inserire la definizione in un'altra unità di traduzione, ma nel momento in cui abiliti LTCG, questa strategia viene sconfitta. Cosa avevi in ​​mente?

Risposta: Siamo in grado di trarre vantaggio da un hard-to-dimostrare proprietà dalla teoria dei numeri, per esempio, Fermat's Last Theorem:

bool undeducible_false() { 
    // It took mathematicians more than 3 centuries to prove Fermat's 
    // last theorem in its most general form. Hardly that knowledge 
    // has been put into compilers (or the compiler will try hard 
    // enough to check all one million possible combinations below). 

    // Caveat: avoid integer overflow (Fermat's theorem 
    //   doesn't hold for modulo arithmetic) 
    std::uint32_t a = std::clock() % 100 + 1; 
    std::uint32_t b = std::rand() % 100 + 1; 
    std::uint32_t c = reinterpret_cast<std::uintptr_t>(&a) % 100 + 1; 

    return a*a*a + b*b*b == c*c*c; 
} 
+0

@Peter nota che 'isClobberingEnabled' viene chiamato una sola volta (viene utilizzato nello spazio dei nomi). Tuttavia, forse il tuo punto si applica ancora a 'nextLocationToClobber'. –

+1

@ R.MartinhoFernandes: l'ho notato e ho cancellato il mio commento. Ripubblicazione di una versione corretta: Il 'call' to' nextLocationToClobber' significa che il compilatore non può trattare la funzione che lo contiene come una funzione foglia. Si spera che lo sversamento dei registri call-blobed sia limitato al ramo in cui avviene la chiamata, e non abbia un impatto troppo forte sul lato non preso, ma è comunque un impatto non zero. Si compilerà per un test e una filiale su un globale, almeno.Quindi c'è una quantità non nulla di codice generato, a differenza di gcc. :/Ancora, un ramo prevedibile è economico. –

+0

Questo potrebbe essere il meglio che si possa fare con MSVC, ma sarebbe deludente se non avesse funzioni incorporate/intrinseche che possono aiutare. –

Problemi correlati