Perché std :: atomic <T> :: is_lock_free () non è statico o constexpr?


9

Qualcuno può dirmi se std :: atomic :: is_lock_free () non è statico e constexpr? Avere questo non statico e / o come non-constexpr non ha senso per me.


3
Sei a conoscenza di is_always_lock_free?
Mike van Dyke,

3
Ho intenzione di gettare "allineamento" là fuori.
Max Langhof,

@MaxLanghof Vuoi dire che non tutte le istanze saranno allineate allo stesso modo?
curioso

1
Mike, no, non ne ero a conoscenza, ma grazie per questo suggerimento; è davvero utile per me. Ma mi chiedo perché c'è una decisione tra is_lock_free () e is_always_lock_free. Non può essere a causa di atomici non allineati, come suggerito da altri qui, dal momento che il linguaggio definisce accessi non allineati per avere comunque un comportamento indefinito.
Bonita Montero,

Risposte:


10

Come spiegato su cppreference :

Tutti i tipi atomici ad eccezione di std :: atomic_flag possono essere implementati utilizzando mutex o altre operazioni di blocco, anziché utilizzare le istruzioni della CPU atomica senza blocco. A volte i tipi atomici possono anche essere liberi da blocchi, ad esempio se solo gli accessi di memoria allineati sono naturalmente atomici su una data architettura, gli oggetti disallineati dello stesso tipo devono usare i blocchi.

Lo standard C ++ raccomanda (ma non richiede) che anche le operazioni atomiche senza blocco siano prive di indirizzi, cioè adatte alla comunicazione tra processi che utilizzano la memoria condivisa.

Come menzionato da molti altri, std::is_always_lock_freepotrebbe essere quello che stai davvero cercando.


Modifica: per chiarire, i tipi di oggetti C ++ hanno un valore di allineamento che limita gli indirizzi delle loro istanze a solo alcuni multipli di potenze di due ( [basic.align]). Questi valori di allineamento sono definiti dall'implementazione per i tipi fondamentali e non devono necessariamente essere uguali alle dimensioni del tipo. Possono anche essere più severi di quanto l'hardware possa effettivamente supportare.

Ad esempio, x86 (principalmente) supporta accessi non allineati. Tuttavia, troverai la maggior parte dei compilatori che hanno alignof(double) == sizeof(double) == 8x86, poiché gli accessi non allineati presentano una serie di svantaggi (velocità, memorizzazione nella cache, atomicità ...). Ma ad esempio #pragma pack(1) struct X { char a; double b; };o alignas(1) double x;ti permette di avere "non allineati" double. Quindi, quando cppreference parla di "accessi di memoria allineati", presumibilmente lo fa in termini di naturale allineamento del tipo per l'hardware, non usando un tipo C ++ in un modo che contraddice i suoi requisiti di allineamento (che sarebbe UB).

Ecco maggiori informazioni: Qual è l'effetto effettivo di accessi non allineati riusciti su x86?

Leggi anche i commenti perspicaci di @Peter Cordes qui sotto!


1
X86 a 32 bit è un buon esempio di dove trovi ABI alignof(double)==4. Ma std::atomic<double>ha ancora alignof() = 8invece di verificare l'allineamento in fase di esecuzione. L'uso di una struttura impacchettata che disallinea gli atomici interrompe l'ABI e non è supportato. (GCC per x86 a 32 bit preferisce fornire un allineamento naturale degli oggetti a 8 byte, ma le regole di impacchettamento hanno la precedenza su quelle e si basano solo alignof(T), ad esempio su i386 System V. G ++ usato per avere un bug in cui atomic<int64_t>all'interno di una struttura potrebbe non essere atomico perché ha appena assunto. GCC (per C non C ++) ha ancora questo bug!)
Peter Cordes,

2
Ma una corretta implementazione di C ++ 20 std::atomic_ref<double>rifiuterà completamente il sotto-allineamento doubleo verificherà l'allineamento in fase di esecuzione su piattaforme in cui è legale per il semplice doublee int64_tper essere meno di quanto naturalmente allineato. (Perché atomic_ref<T>opera su un oggetto che è stato dichiarato come pianura Te ha solo un allineamento minimo alignof(T)senza la possibilità di dargli un allineamento extra.)
Peter Cordes,

2
Vedi gcc.gnu.org/bugzilla/show_bug.cgi?id=62259 per il bug libstdc ++ ora corretto e gcc.gnu.org/bugzilla/show_bug.cgi?id=65146 per il bug C ancora rotto, incluso un puro testcase ISO C11 che mostra lo strappo di un _Atomic int64_tquando compilato con la corrente gcc -m32. Ad ogni modo, il mio punto è che i compilatori reali non supportano l'atomica sotto allineata e non eseguono controlli di runtime (ancora?), Quindi #pragma packo __attribute__((packed))porteranno semplicemente alla non atomicità; gli oggetti segnaleranno comunque che lo sono lock_free.
Peter Cordes,

1
Sì, lo scopo di is_lock_free()è quello di consentire alle implementazioni di funzionare in modo diverso rispetto a quelle attuali; con controlli di runtime basati sull'allineamento effettivo per utilizzare istruzioni atomiche supportate da HW o per utilizzare un blocco.
Peter Cordes,

3

Puoi usare std::is_always_lock_free

is_lock_free dipende dal sistema reale e non può essere determinato al momento della compilazione.

Spiegazione pertinente:

A volte i tipi atomici possono anche essere liberi da blocchi, ad esempio se solo gli accessi di memoria allineati sono naturalmente atomici su una data architettura, gli oggetti disallineati dello stesso tipo devono usare i blocchi.


1
std::numeric_limits<int>::maxdipende dall'architettura, eppure è statico e constexpr. Immagino che non ci sia nulla di sbagliato nella risposta, ma non compro la prima parte del ragionamento
idclev 463035818

1
Non definisce la lingua gli accessi non allineati in modo da avere un comportamento indefinito in modo che una valutazione della libertà di blocco o non in fase di esecuzione sia senza senso?
Bonita Montero,

1
Non ha senso decidere tra accessi allineati e non allineati poiché la lingua definisce quest'ultimo come comportamento indefinito.
Bonita Montero,

@BonitaMontero C'è un senso "non allineato nell'allineamento di oggetti C ++" e "non allineato nel senso dell'hardware". Questi non sono necessariamente gli stessi, ma in pratica lo sono spesso. L'esempio che mostri è uno di questi casi in cui il compilatore ha apparentemente l'assunto incorporato che i due sono gli stessi, il che significa che non ha is_lock_freesenso su quel compilatore .
Max Langhof,

1
Puoi essere abbastanza sicuro che un atomico avrebbe un allineamento corretto se c'è un requisito di allineamento.
Bonita Montero,

1

Ho installato Visual Studio 2019 sul mio PC Windows e questo devenv ha anche un compilatore ARMv8. ARMv8 consente accessi non allineati, ma il confronto e gli scambi, le aggiunte bloccate ecc. Sono obbligati ad essere allineati. E anche il carico puro / archivio puro che utilizza ldpo stp(coppia di carico o coppia di magazzini di registri a 32 bit) è garantito per essere atomico solo quando sono naturalmente allineati.

Quindi ho scritto un piccolo programma per verificare cosa ritorna is_lock_free () per un puntatore atomico arbitrario. Quindi ecco il codice:

#include <atomic>
#include <cstddef>

using namespace std;

bool isLockFreeAtomic( atomic<uint64_t> *a64 )
{
    return a64->is_lock_free();
}

E questo è lo smontaggio di isLockFreeAtomic

|?isLockFreeAtomic@@YA_NPAU?$atomic@_K@std@@@Z| PROC
    movs        r0,#1
    bx          lr
ENDP

Questo è solo returns true, aka 1.

Questa implementazione sceglie di usare in alignof( atomic<int64_t> ) == 8modo che ogni atomic<int64_t>sia allineato correttamente. Ciò evita la necessità di controlli di allineamento di runtime su ogni carico e archivio.

(Nota del redattore: questo è comune; la maggior parte delle implementazioni C ++ nella vita reale funzionano in questo modo. Ecco perché std::is_always_lock_freeè così utile: perché di solito è vero per i tipi dove is_lock_free()è sempre vero.)


1
Sì, la maggior parte delle implementazioni sceglie di dare atomic<uint64_t>e alignof() == 8quindi non è necessario verificare l'allineamento in fase di esecuzione. Questa vecchia API offre loro la possibilità di non farlo, ma sull'attuale HW ha molto più senso solo richiedere l'allineamento (altrimenti UB, ad es. Non atomicità). Anche nel codice a 32 bit in cui int64_tpotrebbe essere presente solo un allineamento a 4 byte, atomic<int64_t>richiede 8 byte. Vedi i miei commenti su un'altra risposta
Peter Cordes,

In parole diverse: se un compilatore sceglie di rendere il alignofvalore per un tipo fondamentale uguale al "buon" allineamento dell'hardware, allora is_lock_free sarà sempre true(e così sarà is_always_lock_free). Il tuo compilatore qui fa esattamente questo. Ma l'API esiste, quindi altri compilatori possono fare cose diverse.
Max Langhof,

1
Puoi essere abbastanza sicuro che se la lingua dice che l'accesso non allineato ha un comportamento indefinito, tutti gli atomici devono essere correttamente allineati. Nessuna implementazione eseguirà alcun controllo di runtime a causa di ciò.
Bonita Montero,

@BonitaMontero Sì, ma non c'è nulla nella lingua che vieti alignof(std::atomic<double>) == 1(quindi non ci sarebbe un "accesso non allineato" in senso C ++, quindi nessun UB), anche se l'hardware può garantire operazioni atomiche senza blocco per doubles su 4 o Confini di 8 byte. Il compilatore dovrebbe quindi utilizzare i blocchi nei casi non allineati (e restituire il valore booleano appropriato da is_lock_free, a seconda della posizione di memoria dell'istanza dell'oggetto).
Max Langhof,
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.