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.

Bonita Montero
fonte
3
Você está ciente is_always_lock_free?
Mike van Dyke
3
Eu vou jogar "alinhamento" lá fora.
precisa
@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.
Bonita Montero

Respostas:

10

Conforme explicado na cppreference :

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).

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!

Max Langhof
fonte
11
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.
Peter Cordes
3

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:

À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.

darune
fonte
11
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:

#include <atomic>
#include <cstddef>

using namespace std;

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

E essa é a desmontagem do isLockFreeAtomic

|?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.)

Bonita Montero
fonte
11
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).
precisa