Perché i compilatori C ++ non ottimizzano questa assegnazione booleana condizionale come assegnazione incondizionata?


117

Considera la seguente funzione:

void func(bool& flag)
{
    if(!flag) flag=true;
}

Mi sembra che se flag ha un valore booleano valido, questo sarebbe equivalente all'impostazione incondizionata su true, in questo modo:

void func(bool& flag)
{
    flag=true;
}

Tuttavia, né gcc né clang lo ottimizzano in questo modo, entrambi generano quanto segue a -O3livello di ottimizzazione:

_Z4funcRb:
.LFB0:
    .cfi_startproc
    cmp BYTE PTR [rdi], 0
    jne .L1
    mov BYTE PTR [rdi], 1
.L1:
    rep ret

La mia domanda è: è solo che il codice è troppo speciale per preoccuparsi di ottimizzarlo, o ci sono buone ragioni per cui tale ottimizzazione sarebbe indesiderata, dato che flagnon è un riferimento volatile? Sembra che l'unica ragione che potrebbe essere è che flagpotesse in qualche modo avere un non- trueoppure falsevalore senza un comportamento indefinito in corrispondenza del punto di leggerlo, ma non sono sicuro se questo è possibile.


8
Hai delle prove che si tratti di una "ottimizzazione"?
David Schwartz

1
@ 200_success Non penso che mettere una riga di codice con markup non funzionante come titolo sia una buona cosa. Se vuoi un titolo più specifico, va bene ma scegli una frase in inglese e cerca di evitare il codice al suo interno (ad esempio, perché i compilatori non ottimizzano le scritture condizionali in scritture incondizionate quando possono dimostrare che sono equivalenti? O simili). Inoltre, poiché i backtick non vengono visualizzati, non usarli nel titolo anche se usi il codice.
Bakuriu

2
@Ruslan, sebbene non sembri eseguire questa ottimizzazione per la funzione stessa, quando può incorporare il codice, sembra che lo faccia per la versione inline. Spesso si traduce solo in una costante di tempo di compilazione 1utilizzata. godbolt.org/g/swe0tc
Evan Teran,

Risposte:


102

Ciò potrebbe influire negativamente sulle prestazioni del programma a causa di considerazioni sulla coerenza della cache . Scrivere flagogni volta che func()viene chiamato sporcherebbe la riga della cache che lo contiene. Ciò avverrà indipendentemente dal fatto che il valore che viene scritto corrisponda esattamente ai bit trovati all'indirizzo di destinazione prima della scrittura.


MODIFICARE

hvd ha fornito un altro buon motivo che impedisce tale ottimizzazione. È un argomento più convincente contro l'ottimizzazione proposta, poiché potrebbe comportare un comportamento indefinito, mentre la mia risposta (originale) ha affrontato solo gli aspetti delle prestazioni.

Dopo un po 'più di riflessione, posso proporre un altro esempio del motivo per cui i compilatori dovrebbero essere fortemente vietati - a meno che non possano dimostrare che la trasformazione è sicura per un particolare contesto - dall'introduzione della scrittura incondizionata. Considera questo codice:

const bool foo = true;

int main()
{
    func(const_cast<bool&>(foo));
}

Con una scrittura incondizionata in func()questo si innesca sicuramente un comportamento indefinito (la scrittura nella memoria di sola lettura terminerà il programma, anche se l'effetto della scrittura sarebbe altrimenti un no-op).


7
Può anche avere un impatto positivo sulle prestazioni poiché ti sbarazzi di un ramo. Quindi non penso che questo caso particolare sia significativo da discutere senza un sistema molto specifico in mente.
Lundin

3
La definizione del comportamento di @Yakk non è influenzata dalla piattaforma di destinazione. Dire che terminerà il programma non è corretto, ma la stessa UB può avere conseguenze di vasta portata, inclusi i demoni nasali.
John Dvorak

16
@Yakk Dipende da cosa si intende per "memoria di sola lettura". No, non è in un chip ROM, ma molto spesso è in una sezione caricata in una pagina che non ha accesso in scrittura abilitato, e otterrai ad esempio un segnale SIGSEGV o un'eccezione STATUS_ACCESS_VIOLATION quando provi a scrivere su di esso.
Casuale 832

5
"questo innesca sicuramente un comportamento indefinito". No. Il comportamento indefinito è una proprietà della macchina astratta. È ciò che dice il codice che determina se UB è presente. I compilatori non possono causarlo (anche se un compilatore con bug può causare il comportamento errato dei programmi).
Eric M Schmidt

7
È l'eliminazione del constpassaggio a una funzione che può modificare i dati che sono l'origine del comportamento indefinito, non la scrittura incondizionata. Dottore, fa male quando faccio questo ...
Spencer

48

A parte la risposta di Leon sulle prestazioni:

Supponiamo che flagsia true. Supponiamo che due thread chiamino costantemente func(flag). La funzione come scritta, in tal caso, non memorizza nulla in flag, quindi dovrebbe essere thread-safe. Due thread accedono alla stessa memoria, ma solo per leggerla. L'impostazione incondizionatamente flagsu truesignifica che due thread diversi scriveranno nella stessa memoria. Questo non è sicuro, non è sicuro anche se i dati scritti sono identici ai dati già presenti.


9
Penso che questo sia il risultato dell'applicazione [intro.races]/21.
Griwes

10
Molto interessante. Quindi ho letto questo come: Al compilatore non è mai permesso di "ottimizzare" un'operazione di scrittura dove la macchina astratta non ne avrebbe una.
Martin Ba

3
@ MartinBa Per lo più così. Ma se il compilatore può dimostrare che non ha importanza, ad esempio perché può provare che nessun altro thread potrebbe avere accesso a quella particolare variabile, allora potrebbe andare bene.

13
Questo non è sicuro solo se il sistema a cui si rivolge il compilatore lo rende non sicuro . Non ho mai sviluppato un sistema in cui scrivere 0x01su un byte che è già 0x01causa un comportamento "non sicuro". Su un sistema con accesso alla memoria word o dword lo sarebbe; ma l'ottimizzatore dovrebbe esserne consapevole. Su un moderno PC o sistema operativo del telefono, non si verificano problemi. Quindi questo non è un motivo valido.
Yakk - Adam Nevraumont

4
@Yakk In realtà, pensando ancora di più, penso che sia giusto dopo tutto, anche per i processori comuni. Penso che tu abbia ragione quando la CPU può scrivere direttamente nella memoria, ma supponiamo che flagsia in una pagina di copia su scrittura. Ora, a livello di CPU, il comportamento potrebbe essere definito (errore di pagina, lascia che sia il sistema operativo a gestirlo), ma a livello di sistema operativo potrebbe essere ancora indefinito, giusto?

13

Non sono sicuro del comportamento di C ++ qui, ma in C la memoria potrebbe cambiare perché se la memoria contiene un valore diverso da zero diverso da 1, rimarrebbe invariato con il controllo, ma cambiato in 1 con il controllo.

Ma poiché non sono molto fluente in C ++, non so se questa situazione sia possibile.


Questo sarebbe ancora vero _Bool?
Ruslan,

5
In C, se la memoria contiene un valore che l'ABI non dice essere valido per il suo tipo, allora è una rappresentazione trap e leggere una rappresentazione trap è un comportamento indefinito. In C ++, questo potrebbe accadere solo durante la lettura di un oggetto non inizializzato e sta leggendo un oggetto non inizializzato che è UB. Ma se riesci a trovare un ABI che dice che qualsiasi valore diverso da zero è valido per il tipo bool/ _Boole significa true, allora in quel particolare ABI, probabilmente hai ragione.

1
@Ruslan Con i compilatori che utilizzano l'ABI Itanium e sui processori ARM, C _Boole C ++ boolsono dello stesso tipo o tipi compatibili che seguono le stesse regole. Con MSVC, hanno le stesse dimensioni e allineamento, ma non esiste una dichiarazione ufficiale sul fatto che utilizzino le stesse regole.
Justin Time - Ripristina Monica il

1
@JustinTime: C <stdbool.h>include un typedef _Bool bool; E sì, su x86 (almeno nell'ABI System V), bool/ _Booldevono essere 0 o 1, con i bit superiori del byte cancellati. Non credo che questa spiegazione sia plausibile.
Peter Cordes

1
@ JustinTime: È vero, avrei dovuto solo sottolineare che ha sicuramente la stessa semantica in tutte le versioni x86 dell'ABI di System V, che è l'argomento di questa domanda. (Posso dirlo perché il primo argomento a è funcstato passato in RDI, mentre Windows userebbe RDX).
Peter Cordes
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.