2010-10-16 15 views
5

curato e raffinato la mia domanda dopo la risposta di valore di JohannesÈ volatile richiesto qui?

bool b = true; 
volatile bool vb = true;  
void f1() { } 
void f2() { b = false; } 

void(* volatile pf)() = &f1; //a volatile pointer to function 

int main() 
{ 
    //different threads start here, some of which may change pf 
    while(b && vb) 
    { 
     pf(); 
    } 
} 

Quindi, cerchiamo di dimenticare la sincronizzazione per un po '. La domanda è se b deve essere dichiarato volatile. Ho letto lo standard e il tipo di conoscenza della definizione formale di semantica volatile (li ho quasi capiti, la parola è quasi la chiave). Ma siamo un po 'informali qui. Se il compilatore vede che nel ciclo non c'è modo per cambiare b allora a meno che b non sia volatile, può ottimizzarlo e assumere che sia equivalente a while(vb). La domanda è, in questo caso, pf è esso stesso volatile, così il compilatore ha la possibilità di assumere che b non cambierà nel ciclo anche se b non è volatile?

Si prega di astenersi da commenti e risposte che affrontano lo stile di questo pezzo di codice, questo non è un esempio del mondo reale, questa è una domanda teorica sperimentale. Commenti e risposte che, oltre a rispondere alla mia domanda, affronteranno anche la semantica di volatili in modo più dettagliato e che pensate di aver frainteso sono molto ben accette.

Spero che la mia domanda sia chiara. TIA

Editing una volta di più:
che dire di questo?

bool b = true; 
volatile bool vb = true; 
void f1() {} 
void f2() {b = false;} 
void (*pf)() = &f1; 

#include <iosrteam> 
int main() 
{ 
    //threads here 

    while(b && vb) 
    { 
     int x; 
     std::cin >> x; 
     if(x == 0) 
     pf = &f1; 
     else 
     pf = &f2;  
     pf(); 
    } 
} 

C'è una differenza principale tra i due programmi. Se sì, qual è la differenza?

+0

Non conosco C molto bene. Se questo codice è valido anche C (a parte il bool che credo non esista in C), per favore dimmi che aggiungo anche il tag C alla domanda –

+0

è valido C se si #include ybungalobill

+0

si prega di primo codice la tua domanda, e poi chiedi. Non è divertente cambiare continuamente la propria risposta. Inoltre, quali sono i thread autorizzati a cambiare (presupponendo una corretta sincronizzazione)? Ci sono mutex attorno a certe parti? (intorno alla parte "assign-pf + call"?) –

risposta

3

La domanda è, in questo caso pf è esso stesso volatile, così il compilatore ha la possibilità di assumere che b non cambierà nel ciclo anche se b non è volatile?

Non può, perché dici che pf potrebbe essere modificato dal altri thread, e questo cambia indirettamente b se pf è chiamato poi dal ciclo while. Quindi, mentre in teoria non è necessario leggere b normalmente, in pratica deve leggerlo per determinare se deve essere cortocircuito (quando b diventa false non deve leggere vb un'altra volta).


risposta alla seconda parte

In questo caso pf non è volatile più, quindi il compilatore può sbarazzarsi di esso e vedere che f1 ha un corpo vuoto e f2 set b false. Si potrebbe ottimizzare main come segue

int main() 
{ 
    // threads here (which you say can only change "vb") 

    while(vb) 
    { 
     int x; 
     std::cin >> x; 
     if(x != 0) 
     break;  
    } 
} 

risposta alla revisione più vecchia

Una condizione per il compilatore per essere consentito di ottimizzare il ciclo di distanza è che il ciclo non accedere o modificare qualsiasi volatili oggetto (Vedi [stmt.iter] p5 in n3126). Lo fai qui, quindi non può ottimizzare il loop. In C++ 03 un compilatore non era autorizzato a ottimizzare anche la versione non volatile di quel loop (ma i compilatori lo facevano comunque).

Si noti che un'altra condizione per essere in grado di ottimizzarlo è che il ciclo non contiene operazioni di sincronizzazione o atomiche. In un programma multithread, tuttavia, dovrebbe essere presente comunque. Quindi, anche se ti sbarazzi di quello volatile, se il tuo programma è correttamente codificato non penso che il compilatore possa ottimizzarlo completamente.

+0

@Johannes: Ok, non è possibile ottimizzare il ciclo, ma è possibile ottimizzare b lontano e NON leggere il suo valore come condizione del ciclo? Cioè convertirlo in un ciclo infinito? Per evitare di dire loop infinito, diciamo che è stato mentre (b && some_other_volatile_bool). Il compilatore è autorizzato a convertirlo mentre (some_other_volatile_bool)? –

+0

@Armen di sicuro, se 'b' non viene mai modificato, C++ 03 lo consente interamente. Deve essere volatile perché non sia autorizzato a ottimizzarlo. –

+0

@Johannes: Modificato la mia domanda. Inoltre non posso dire di essere completamente soddisfatto della risposta. Vedete, la vostra risposta implica anche che se un loop contiene una chiamata tramite un pointer-to-function che è impostato all'interno del loop in base all'input dell'utente, il compilatore può assumere che b non cambi, che non voglio credere sia vero . Dovrei? –

0

volatile ti fa male solo se pensi di poter beneficiare di un'ottimizzazione che non può essere eseguita o comunica qualcosa che non è vero.

Nel tuo caso hai detto che queste variabili possono essere modificate da altri thread. Leggere il codice, questa è la mia ipotesi quando vedo una volatilità, quindi dal punto di vista di un manutentore, è una cosa buona - mi sta dando informazioni aggiuntive (che è vero).

Non so se le ottimizzazioni valgano il tentativo di salvataggio poiché hai detto che questo non è il codice reale, ma se non lo sono non ci sono motivi per non usare volatili.

Non utilizzare volatile quando si presume che si verifichi un comportamento errato, poiché le ottimizzazioni stanno cambiando il significato del codice.

Mi preoccupo di codificare i minimi dello standard e del comportamento dei compilatori perché cose come questa possono cambiare e anche se non lo fanno, il codice cambia (che potrebbe influenzare il compilatore) - quindi, a meno che non si stia cercando per i miglioramenti della micro-ottimizzazione su questo specifico codice, lo lascerei volatile.

2

I requisiti esatti su volatile nello standard C++ corrente in un caso come questo sono, a quanto ho capito, non del tutto ben definiti dallo standard, poiché lo standard non si occupa veramente di multi-threading. È fondamentalmente un suggerimento per il compilatore. Così, invece, affronterò cosa succede in un tipico compilatore.

Prima di tutto, supponiamo che il compilatore stia compilando le tue funzioni in modo indipendente e quindi collegandole tra loro. In entrambi gli esempi, hai un ciclo in cui stai controllando una variabile e chiamando un puntatore a funzione. Nel contesto di tale funzione, il compilatore non ha idea di cosa funzioni la funzione che sta dietro quel puntatore, e quindi deve sempre ricaricare dalla memoria dopo averlo chiamato. Quindi, volatile è irrilevante lì.

espansione che al tuo primo caso reale, e permettendo al compilatore di fare ottimizzazioni tutto il programma, perché pf è volatile il compilatore ha ancora idea di cosa sta per essere indicando (non può neppure assumere è sia f1 oppure f2!), e quindi non è possibile formulare alcuna ipotesi su ciò che non sarà modificato nella chiamata al puntatore di funzione - e quindi volatile su è ancora irrilevante.

Il tuo secondo caso è in realtà più semplice - vb in esso è un'aringa rossa. Se si elimina ciò, è possibile vedere che anche in semantica completamente single-threaded, la chiamata al puntatore di funzione può modificare b. Non stai facendo nulla con un comportamento indefinito, e quindi il programma deve funzionare correttamente senza volatile - ricorda che, se non stai considerando una situazione con ritocchi di thread esterni, volatile è un no-op. Pertanto, senza vb nella foto, non è possibile che sia necessario volatile ed è abbastanza chiaro che l'aggiunta di vb non modifica nulla.

Quindi, in breve: non è necessario volatile in entrambi i casi.La differenza, nella misura in cui ce n'è una, è che nel primo caso se fp non fosse volatile, un compilatore sufficientemente avanzato potrebbe eventualmente ottimizzare b, mentre nel secondo caso non può nemmeno senza volatile nel programma. In pratica, non mi aspetto che nessun compilatore realizzi effettivamente questa ottimizzazione.

+0

Nel mio secondo esempio, il compilatore in genere capisce che pf è o f1 o f2, analizza entrambe le funzioni e vede che b può cambiare, o la semplice invocazione di un puntatore a funzione è sufficiente per astenersi da ottimizzazione delle letture da una variabile globale? –

+0

E, per inciso, C++ 0x si occupa di multi-threading ... molto –

+0

Giusto; Stavo pensando all'attuale standard C++ (e modificato per renderlo chiaro). In ogni caso, sarei sorpreso se nel secondo esempio il compilatore faccia qualche analisi su cosa può puntare il puntatore alla funzione; Non mi aspetterei che in generale sarebbe un'analisi produttiva (anche se ovviamente è utile in questo piccolo esempio). –