7

Ho una domanda riguardante le ottimizzazioni che il compilatore può potenzialmente fare.Ottimizzazione del compilatore, thread sicuro?

Il codice qui sotto parlerà da sé (questo è un esempio):

typedef struct test 
{ 
    short i; 
}    s_test; 

int function1(char *bin) 
{ 
    s_test foo; 

    lock(gmutex); 
    foo.i = *(int*)bin * 8; 
    unlock(gmutex); 

    sleep(5); 
    // 
    // Here anything can happen to *bin in another thread 
    // an inline example here could be: *(volatile int *)bin = 42; 
    // 

    int b = foo.i + sizeof(char*); 

    return (b > 1000); 
} 

Potrebbe il compilatore mai sostituire le ultime righe con

return ((*(int*)bin * 8 + sizeof(char*)) > 1000); 

Non sembra essere il caso con -O2 o -O3 con gcc 4.4 ma potrebbe essere il caso con altri compilatori e con altre bandiere di compilazione?

+5

Sostituire una variabile con un'espressione non è esattamente un'ottimizzazione, vero? – EJP

+1

Per alcuni problemi relativi all'ottimizzazione del compilatore e alla sicurezza del thread, consultare http: // StackOverflow.it/questions/2001913/c0x-memory-model-and-speculative-loads-memorizza ed in particolare i documenti ad essi collegati. – janneb

+0

Le modifiche a * bin possono verificarsi anche durante il mutex a meno che non si protegga ogni accesso a _ALL_ oggetti di memoria * bin può puntare a con gmutex. L'unica cosa che è protetta dal tuo mutex è la variabile locale foo che forse non è ciò che volevi. – slartibartfast

risposta

5

Non penso che il compilatore eseguirà un tale tipo di ottimizzazione.

questa è la funzione, il compilatore non può assumere che il valore indicato da bin verrà modificato nella funzione di sblocco o meno.

per esempio, forse bin proviene da un globo. Quindi l'ottimizzazione per bin non può attraversare la chiamata di funzione.

+1

Il compilatore non può certamente fare questa ottimizzazione. Altrimenti non ci sarebbe un modo sicuro per implementare una funzione di blocco. – ugoren

+2

@ugoren, il modello di blocco e thread viene fornito solo con C11. Prima di tutte le scommesse sono spenti per fare affidamento sullo stesso standard C in merito a qualsiasi motivazione in tal senso. Quindi la programmazione dei thread con C prima di C11 richiede una cura speciale. In particolare l'aliasing come nell'esempio non è una buona idea. Qui probabilmente è ok dato che 'bin' è' char * 'ma la mia ipotesi sarebbe che Dpp non controlli molto bene questo aspetto di C :) –

+0

A meno che non si abbia il flag" presumere nessun aliasing tra le funzioni "(e in realtà avere una funzione che può cambiare una variabile locale). Fortunatamente, non vedo più questa opzione documentata su MSDN. – selbie

4

Il tuo esempio è inutilmente complicato perché stai leggendo bin attraverso un tipo diverso da quello dichiarato. Le regole di aliasing sono piuttosto complicate, char è addirittura speciale, non vorrei commentare in merito.

Supponendo che la dichiarazione sia int* bin (e quindi non si debba eseguire il cast del puntatore) un compilatore non avrebbe il diritto di riordinare le istruzioni tra le chiamate di funzione, formando i cosiddetti punti di sequenza. Il valore di *bin prima e dopo la chiamata a unlock potrebbe essere diverso, pertanto ha per caricare il valore dopo la chiamata.

Edit: come notato da Slartibartfast, per questo argomento è essenziale che unlock è (o contiene) una chiamata di funzione e non è solo una macro che si risolve in una sequenza di operazioni dal compilatore.

+0

Siamo spiacenti di intercettarlo di nuovo: anche se unlock() è solo un'istruzione conterrà un ";" che è un punto di sequenza come una chiamata di funzione che stai provando a tirare di fronte alla tenda: è solo che in questo caso non sarà di aiuto. Per avere un'idea delle complessità del C multithread e degli standard fino al C99, si prega di dare un'occhiata agli archivi comp.lang.c e comp.std.c. – slartibartfast

1

Come ho detto nella risposta diretta: il tuo mutex non protegge nulla IMHO. L'array di caratteri in cui * bin si inserisce può essere modificato durante l'accesso int in modo che, a seconda della macchina, non si ottenga una vista coerente sulla memoria a cui si desidera accedere. Torna alla tua domanda: un compilatore non trasformerà il codice sorgente nella sequenza che hai immaginato, ma potrebbe benissimo produrre un linguaggio macchina che in effetti si comporterà come il tuo codice sorgente. Se è in grado di ispezionare le funzioni blocca, sblocca e dorme (che sembrano essere comunque macro) e può dedurre che non vi è alcun effetto collaterale sulle posizioni di memoria coinvolte E non vi è alcun significato definito dall'implementazione per le chiamate ad es. sleep() che renderebbe non valido ("memorizzato nella cache", sebbene lo standard non usi questo termine) valori non validi, allora ha il diritto di produrre una sequenza di istruzioni come quella che hai dato. C (fino a C99) è intrinsecamente a thread singolo e il compilatore può impiegare qualsiasi strategia di ottimizzazione che vuole purché il codice si comporti come se fosse eseguito sulla macchina ipotetica ideale. I punti di sequenza che Jens ha menzionato non influiscono sulla correttezza in condizioni di thread singolo: il compilatore potrebbe contenere foo in un registro durante l'intera funzione o potrebbe anche alias foo.i con la posizione di memoria puntata da * bin, quindi questo codice è intrinsecamente pericoloso (anche se penso che non mostrerà questo comportamento sulla maggior parte dei compilatori).

+0

Hai ragione se supponi che la funzione 'unlock' possa non essere una chiamata di funzione ma una macro che si espande in una sequenza di operazioni che sono note al compilatore. Stavo assumendo l'altro caso, che questa è effettivamente una chiamata di funzione. –

+2

Anche così non ti salverà. Se il compilatore può esaminare la funzione, potrebbe benissimo ottimizzarla su di essa. Avere foo come automatico è una premessa un po 'sfortunata per questo esempio. – slartibartfast

+0

Non c'è problema se 'lock' e' unlock' non sono bacati. Implementarli in una macro, o una funzione nello stesso ambito, che in qualche modo non impedisce il riordino è uno dei tanti modi sottili per scrivere primitive che bloccano i buggy. Inoltre, il blocco protegge gli accessi a '* bin' se viene tenuto premuto ogni volta che si accede a questa memoria. Chiusura in un solo posto è senza valore. – ugoren