Por que std :: atomic <T> :: is_lock_free () não estático, assim como constexpr?
9
Alguém pode me dizer se std :: atomic :: is_lock_free () não é estático, assim como constexpr? Tê-lo não estático e / ou não constexpr não faz sentido para mim.
@MaxLanghof Você quer dizer que nem todas as instâncias serão alinhadas da mesma maneira?
curiousguy
11
Mike, não, eu não estava ciente, mas obrigado por essa dica; É realmente útil para mim. Mas estou me perguntando por que há uma decisão entre is_lock_free () e is_always_lock_free. Não pode ser por causa de átomos desalinhados, o que outros sugerem aqui, já que a linguagem define acessos desalinhados para ter um comportamento indefinido.
Todos os tipos atômicos, exceto std :: atomic_flag, podem ser implementados usando mutexes ou outras operações de bloqueio, em vez de usar as instruções da CPU atômica sem bloqueio. Às vezes, os tipos atômicos também podem ser livres de bloqueios; por exemplo, se apenas os acessos à memória alinhados são naturalmente atômicos em uma determinada arquitetura, objetos desalinhados do mesmo tipo precisam usar bloqueios.
O padrão C ++ recomenda (mas não exige) que operações atômicas sem bloqueio também sejam livres de endereço, ou seja, adequadas para comunicação entre processos usando memória compartilhada.
Conforme mencionado por várias outras pessoas, std::is_always_lock_freepode ser o que você realmente está procurando.
Editar: para esclarecer, os tipos de objetos C ++ têm um valor de alinhamento que restringe os endereços de suas instâncias a apenas determinados múltiplos de potências de dois ( [basic.align]). Esses valores de alinhamento são definidos pela implementação para tipos fundamentais e não precisam ser iguais ao tamanho do tipo. Eles também podem ser mais rigorosos do que o que o hardware realmente suporta.
Por exemplo, x86 (principalmente) suporta acessos não alinhados. No entanto, você encontrará a maioria dos compiladores que possuem o alignof(double) == sizeof(double) == 8x86, pois os acessos desalinhados têm uma série de desvantagens (velocidade, cache, atomicidade ...). Mas, por exemplo, #pragma pack(1) struct X { char a; double b; };ou alignas(1) double x;permite que você tenha "desalinhados" doubles. Portanto, quando cppreference fala sobre "acessos de memória alinhados", presumivelmente o faz em termos do alinhamento natural do tipo para o hardware, não usando um tipo C ++ de uma maneira que contradiga seus requisitos de alinhamento (que seria UB).
X86 de 32 bits é um bom exemplo de onde você encontra ABIs alignof(double)==4. Mas std::atomic<double>ainda tem, em alignof() = 8vez de verificar o alinhamento em tempo de execução. O uso de uma estrutura empacotada que subalinha o atômico interrompe o ABI e não é suportado. (O GCC para x86 de 32 bits prefere dar um alinhamento natural aos objetos de 8 bytes, mas as regras de empacotamento de estruturas substituem isso e são baseadas apenas alignof(T), por exemplo, no i386 System V. O G ++ costumava ter um erro em que atomic<int64_t>dentro de uma estrutura pode não ser atômico porque ele só assumiu GCC (para C não C ++) ainda tem esse bug).!
Peter Cordes
2
Mas uma implementação correta do C ++ 20 std::atomic_ref<double>rejeitará completamente o sub-alinhamento doubleou verificará o alinhamento no tempo de execução nas plataformas em que é legal doublee simples e int64_tmenos alinhado naturalmente. (Porque atomic_ref<T>opera sobre um objeto que foi declarada como uma planície T, e tem apenas uma harmonização mínima de alignof(T)sem a oportunidade de dar-lhe o alinhamento extra.)
Peter Cordes
2
Consulte gcc.gnu.org/bugzilla/show_bug.cgi?id=62259 para obter o bug libstdc ++ agora corrigido e gcc.gnu.org/bugzilla/show_bug.cgi?id=65146 para obter o bug C ainda quebrado, incluindo um caso de teste ISO C11 puro que mostra rasgo de um _Atomic int64_tcompilado com corrente gcc -m32. Enfim, o que quero dizer é que os compiladores reais não suportam átomos sub-alinhados e não fazem verificações em tempo de execução (ainda?); Portanto, #pragma packou __attribute__((packed))apenas levarão à não-atomicidade; objetos ainda relatam que são lock_free.
Peter Cordes
11
Mas sim, o objetivo is_lock_free()é permitir que as implementações funcionem de maneira diferente da atual; com verificações de tempo de execução baseadas no alinhamento real para usar instruções atômicas suportadas por HW ou usar uma trava.
is_lock_free depende do sistema real e não pode ser determinado em tempo de compilação.
Explicação relevante:
Às vezes, os tipos atômicos também podem ser livres de bloqueios, por exemplo, se apenas os acessos alinhados à memória são naturalmente atômicos em uma determinada arquitetura, objetos desalinhados do mesmo tipo precisam usar bloqueios.
std::numeric_limits<int>::maxdepende da arquitetura, ainda é estático e constexpr. Eu acho que não há nada errado na resposta, mas eu não comprar a primeira parte do raciocínio
idclev 463035818
11
Não define os acessos desalinhados do idioma para ter um comportamento indefinido, de modo que uma avaliação da ausência de bloqueio ou não em tempo de execução seria um absurdo?
Bonita Montero
11
Não faz sentido decidir entre acessos alinhados e desalinhados, pois o idioma define o último como comportamento indefinido.
Bonita Montero
@BonitaMontero Há um sentido "desalinhado no alinhamento de objetos C ++" e "desalinhado no sentido que o hardware gosta". Essas não são necessariamente as mesmas, mas na prática são frequentemente. O exemplo que você mostra é uma instância em que o compilador aparentemente tem a suposição interna de que os dois são iguais - o que significa apenas que não is_lock_freefaz sentido nesse compilador .
precisa
11
Você pode ter certeza de que um atômico teria alinhamento adequado se houver um requisito de alinhamento.
Bonita Montero
1
Instalei o Visual Studio 2019 no meu PC com Windows e este devenv também possui um compilador ARMv8. O ARMv8 permite acessos desalinhados, mas a comparação e trocas, adições bloqueadas etc. são obrigatórias para serem alinhadas. E também carga pura / armazenamento puro usando ldpor stp(par de carga ou par de registros de 32 bits) só são garantidos como atômicos quando alinhados naturalmente.
Então, escrevi um pequeno programa para verificar o que is_lock_free () retorna para um ponteiro atômico arbitrário. Então aqui está o código:
|?isLockFreeAtomic@@YA_NPAU?$atomic@_K@std@@@Z| PROC
movs r0,#1
bx lr
ENDP
Isto é apenas returns true, aka 1.
Essa implementação escolhe usar alignof( atomic<int64_t> ) == 8para que todos atomic<int64_t>estejam alinhados corretamente. Isso evita a necessidade de verificações de alinhamento do tempo de execução em cada carregamento e armazenamento.
(Nota do editor: isso é comum; a maioria das implementações da vida real em C ++ funciona dessa maneira. É por std::is_always_lock_freeisso que é tão útil: porque geralmente é verdade para tipos onde is_lock_free()é verdade.)
Sim, a maioria das implementações escolhe dar atomic<uint64_t>e, alignof() == 8portanto, não precisa verificar o alinhamento no tempo de execução. Essa API antiga oferece a opção de não fazer isso, mas no HW atual, faz muito mais sentido exigir alinhamento (caso contrário, UB, por exemplo, não atomicidade). Mesmo no código de 32 bits, onde int64_tpode ter apenas alinhamento de 4 bytes, atomic<int64_t>requer 8 bytes. Veja meus comentários em outra resposta
Peter Cordes
Colocar em palavras diferentes: Se um compilador escolhe fazer alignofvalor para um tipo fundamental o mesmo que o "bom" alinhamento do hardware, em seguida,is_lock_free será sempre true(e assim vai is_always_lock_free). Seu compilador aqui faz exatamente isso. Mas a API existe para que outros compiladores possam fazer coisas diferentes.
precisa
11
Você pode ter certeza de que, se a linguagem disser que o acesso desalinhado tem um comportamento indefinido, todos os átomos precisam estar alinhados corretamente. Nenhuma implementação fará nenhuma verificação de tempo de execução por causa disso.
Bonita Montero
@BonitaMontero Sim, mas não há nada na linguagem que proíba alignof(std::atomic<double>) == 1(portanto, não haveria "acesso desalinhado" no sentido C ++, portanto, não UB), mesmo que o hardware possa garantir operações atômicas livres de bloqueio para doubles on 4 ou Limites de 8 bytes. O compilador precisaria usar bloqueios nos casos não alinhados (e retornar o valor booleano apropriado de is_lock_free, dependendo do local da memória da instância do objeto).
is_always_lock_free
?Respostas:
Conforme explicado na cppreference :
Conforme mencionado por várias outras pessoas,
std::is_always_lock_free
pode ser o que você realmente está procurando.Editar: para esclarecer, os tipos de objetos C ++ têm um valor de alinhamento que restringe os endereços de suas instâncias a apenas determinados múltiplos de potências de dois (
[basic.align]
). Esses valores de alinhamento são definidos pela implementação para tipos fundamentais e não precisam ser iguais ao tamanho do tipo. Eles também podem ser mais rigorosos do que o que o hardware realmente suporta.Por exemplo, x86 (principalmente) suporta acessos não alinhados. No entanto, você encontrará a maioria dos compiladores que possuem o
alignof(double) == sizeof(double) == 8
x86, pois os acessos desalinhados têm uma série de desvantagens (velocidade, cache, atomicidade ...). Mas, por exemplo,#pragma pack(1) struct X { char a; double b; };
oualignas(1) double x;
permite que você tenha "desalinhados"double
s. Portanto, quando cppreference fala sobre "acessos de memória alinhados", presumivelmente o faz em termos do alinhamento natural do tipo para o hardware, não usando um tipo C ++ de uma maneira que contradiga seus requisitos de alinhamento (que seria UB).Aqui estão mais informações: Qual é o efeito real de acessos não alinhados com êxito no x86?
Confira também os comentários perspicazes de @Peter Cordes abaixo!
fonte
alignof(double)==4
. Masstd::atomic<double>
ainda tem, emalignof() = 8
vez de verificar o alinhamento em tempo de execução. O uso de uma estrutura empacotada que subalinha o atômico interrompe o ABI e não é suportado. (O GCC para x86 de 32 bits prefere dar um alinhamento natural aos objetos de 8 bytes, mas as regras de empacotamento de estruturas substituem isso e são baseadas apenasalignof(T)
, por exemplo, no i386 System V. O G ++ costumava ter um erro em queatomic<int64_t>
dentro de uma estrutura pode não ser atômico porque ele só assumiu GCC (para C não C ++) ainda tem esse bug).!std::atomic_ref<double>
rejeitará completamente o sub-alinhamentodouble
ou verificará o alinhamento no tempo de execução nas plataformas em que é legaldouble
e simples eint64_t
menos alinhado naturalmente. (Porqueatomic_ref<T>
opera sobre um objeto que foi declarada como uma planícieT
, e tem apenas uma harmonização mínima dealignof(T)
sem a oportunidade de dar-lhe o alinhamento extra.)_Atomic int64_t
compilado com correntegcc -m32
. Enfim, o que quero dizer é que os compiladores reais não suportam átomos sub-alinhados e não fazem verificações em tempo de execução (ainda?); Portanto,#pragma pack
ou__attribute__((packed))
apenas levarão à não-atomicidade; objetos ainda relatam que sãolock_free
.is_lock_free()
é permitir que as implementações funcionem de maneira diferente da atual; com verificações de tempo de execução baseadas no alinhamento real para usar instruções atômicas suportadas por HW ou usar uma trava.Você pode usar
std::is_always_lock_free
is_lock_free
depende do sistema real e não pode ser determinado em tempo de compilação.Explicação relevante:
fonte
std::numeric_limits<int>::max
depende da arquitetura, ainda é estático econstexpr
. Eu acho que não há nada errado na resposta, mas eu não comprar a primeira parte do raciocíniois_lock_free
faz sentido nesse compilador .Instalei o Visual Studio 2019 no meu PC com Windows e este devenv também possui um compilador ARMv8. O ARMv8 permite acessos desalinhados, mas a comparação e trocas, adições bloqueadas etc. são obrigatórias para serem alinhadas. E também carga pura / armazenamento puro usando
ldp
orstp
(par de carga ou par de registros de 32 bits) só são garantidos como atômicos quando alinhados naturalmente.Então, escrevi um pequeno programa para verificar o que is_lock_free () retorna para um ponteiro atômico arbitrário. Então aqui está o código:
E essa é a desmontagem do isLockFreeAtomic
Isto é apenas
returns true
, aka1
.Essa implementação escolhe usar
alignof( atomic<int64_t> ) == 8
para que todosatomic<int64_t>
estejam alinhados corretamente. Isso evita a necessidade de verificações de alinhamento do tempo de execução em cada carregamento e armazenamento.(Nota do editor: isso é comum; a maioria das implementações da vida real em C ++ funciona dessa maneira. É por
std::is_always_lock_free
isso que é tão útil: porque geralmente é verdade para tipos ondeis_lock_free()
é verdade.)fonte
atomic<uint64_t>
e,alignof() == 8
portanto, não precisa verificar o alinhamento no tempo de execução. Essa API antiga oferece a opção de não fazer isso, mas no HW atual, faz muito mais sentido exigir alinhamento (caso contrário, UB, por exemplo, não atomicidade). Mesmo no código de 32 bits, ondeint64_t
pode ter apenas alinhamento de 4 bytes,atomic<int64_t>
requer 8 bytes. Veja meus comentários em outra respostaalignof
valor para um tipo fundamental o mesmo que o "bom" alinhamento do hardware, em seguida,is_lock_free
será sempretrue
(e assim vaiis_always_lock_free
). Seu compilador aqui faz exatamente isso. Mas a API existe para que outros compiladores possam fazer coisas diferentes.alignof(std::atomic<double>) == 1
(portanto, não haveria "acesso desalinhado" no sentido C ++, portanto, não UB), mesmo que o hardware possa garantir operações atômicas livres de bloqueio paradouble
s on 4 ou Limites de 8 bytes. O compilador precisaria usar bloqueios nos casos não alinhados (e retornar o valor booleano apropriado deis_lock_free
, dependendo do local da memória da instância do objeto).