O que significa "acontece fortemente antes"?

9

A frase "fortemente acontece antes" é usada várias vezes no rascunho do padrão C ++.

Por exemplo: Rescisão [basic.start.term] / 5

Se a conclusão da inicialização de um objeto com duração de armazenamento estático ocorrer fortemente antes de uma chamada para std :: atexit (consulte [support.start.term]), a chamada para a função transmitida para std :: atexit é sequenciado antes da chamada para o destruidor do objeto. Se uma chamada para std :: atexit ocorrer fortemente antes da conclusão da inicialização de um objeto com duração de armazenamento estático, a chamada para o destruidor do objeto será sequenciada antes da chamada para a função passada para std :: atexit . Se uma chamada para std :: atexit acontecer fortemente antes de outra chamada para std :: atexit, a chamada para a função passada para a segunda chamada std :: atexit será sequenciada antes da chamada para a função passada para o primeira chamada std :: atexit.

E definido em Corridas de dados [intro.races] / 12

Uma avaliação A ocorre fortemente antes de uma avaliação D se,

(12.1) A é sequenciado antes de D, ou

(12.2) A sincroniza com D, e ambos A e D são operações atômicas sequencialmente consistentes ([atomics.order]), ou

(12.3) existem avaliações B e C, de modo que A é sequenciado antes de B, B simplesmente acontece antes de C e C é sequenciado antes de D, ou

(12.4) há uma avaliação B, de modo que A ocorre fortemente antes de B e B ocorre fortemente antes de D.

[Nota: Informalmente, se A acontecer fortemente antes de B, então A parecerá ser avaliado antes de B em todos os contextos. Fortemente acontece antes de excluir operações de consumo. - nota final]

Por que "fortemente aconteceu antes" foi introduzido? Intuitivamente, qual é a diferença e a relação com "acontece antes"?

O que significa "A parece ser avaliado antes de B em todos os contextos" na nota?

(Nota: a motivação para esta pergunta são os comentários de Peter Cordes sob esta resposta .)

Projeto adicional de cotação padrão (graças a Peter Cordes)

Ordem e consistência [atomics.order] / 4

Existe uma única ordem total S em todas as operações memory_order :: seq_cst, incluindo cercas, que atendem às seguintes restrições. Primeiro, se A e B são operações memory_order :: seq_cst e A ocorre fortemente antes de B, então A precede B em S. Segundo, para cada par de operações atômicas A e B em um objeto M, onde A é ordenado por coerência antes de B, as quatro condições a seguir devem ser atendidas por S:

(4.1) se A e B são ambas operações memory_order :: seq_cst, então A precede B em S; e

(4.2) se A é uma operação memory_order :: seq_cst e B ocorre antes de uma memory_order :: seq_cst cerca Y, então A precede Y em S; e

(4.3) se um memory_order :: seq_cst fence X acontecer antes de A e B ser uma operação memory_order :: seq_cst, X precederá B em S; e

(4.4) se um memory_order :: seq_cst fence X acontecer antes de A e B acontecer antes de um memory_order :: seq_cst fence Y, então X precederá Y em S.

curiousguy
fonte
11
O esboço atual do padrão também faz referência a "A acontece fortemente antes de B" como condição para uma regra que solicita seq_cst, no Atomics 31.4 Ordem e consistência: 4 . Isso não está no padrão C ++ 17 n4659 , onde 32.4 - 3 define a existência de uma única ordem total de operações seq_cst consistentes com a ordem "acontece antes" e as ordens de modificação para todos os locais afetados ; o "fortemente" foi adicionado em um rascunho posterior.
Peter Cordes
2
@ PeterCordes Eu acho que o comentário excluindo consumir, afirmando que é HB "em todo o contexto" / "forte" e falar sobre chamadas para indicadores de função é uma espécie de oferta inoperante. Se um programa multithread chama atexit()um thread e exit()outro, não é suficiente que os inicializadores carreguem apenas uma dependência baseada em consumo, porque os resultados diferem se forem exit()invocados pelo mesmo thread. Uma resposta minha mais antiga dizia respeito a essa diferença.
Iwillnotexist Idonotexist
@IwillnotexistIdonotexist Você pode sair de um programa MT? Não é fundamentalmente uma ideia quebrada?
curiousguy
11
@curiousguy Esse é o objetivo de exit(). Qualquer encadeamento pode matar o programa inteiro saindo, ou o encadeamento principal pode sair returnpressionando -ing. Isso resulta na chamada de atexit()manipuladores e na morte de todos os threads, independentemente do que eles estavam fazendo.
Iwillnotexist Idonotexist

Respostas:

5

Por que "fortemente aconteceu antes" foi introduzido? Intuitivamente, qual é a diferença e a relação com "acontece antes"?

Prepare-se para "simplesmente acontece antes" também! Dê uma olhada neste instantâneo atual do cppref https://en.cppreference.com/w/cpp/atomic/memory_order

insira a descrição da imagem aqui

Parece que "simplesmente acontece antes" é adicionado no C ++ 20.

Simplesmente acontece - antes

Independentemente dos encadeamentos, a avaliação A simplesmente acontece antes da avaliação B, se alguma das seguintes situações for verdadeira:

1) A é sequenciado antes de B

2) A sincroniza com B

3) A simplesmente acontece antes de X e X simplesmente acontece antes de B

Nota: sem operações de consumo, as relações simplesmente acontecem antes e depois acontecem antes.

Portanto, Simply-HB e HB são os mesmos, exceto pelo modo como lidam com as operações de consumo. Veja HB

Acontece antes

Independentemente dos encadeamentos, a avaliação A acontece antes da avaliação B, se alguma das seguintes situações for verdadeira:

1) A é sequenciado antes de B

2) Um encadeamento ocorre antes de B

A implementação é necessária para garantir que a relação aconteça antes seja acíclica, introduzindo sincronização adicional, se necessário (só pode ser necessária se uma operação de consumo estiver envolvida, consulte Batty et al)

Como eles diferem em relação ao consumo? Consulte Inter-Thread-HB

Inter-thread acontece antes

Entre encadeamentos, avaliação Um encadeamento ocorre antes da avaliação B se alguma das opções a seguir for verdadeira

1) A sincroniza com B

2) A é ordenado por dependência antes de B

3) ...

...

Uma operação ordenada por dependência (ou seja, usa liberação / consumo) é HB, mas não necessariamente Simply-HB.

Consumir é mais descontraído do que adquirir, então, se eu entendi direito, o HB é mais descontraído do que o Simply-HB.

Fortemente acontece antes

Independentemente dos encadeamentos, a avaliação A ocorre fortemente antes da avaliação B, se alguma das seguintes situações for verdadeira:

1) A é sequenciado antes de B

2) A sincroniza com B, e ambos A e B são operações atômicas sequencialmente consistentes

3) A é sequenciado antes de X, X simplesmente acontece antes de Y e Y é sequenciado antes de B

4) A acontece fortemente antes de X e X acontece fortemente antes de B

Nota: informalmente, se A acontece fortemente antes de B, então A parece ser avaliado antes de B em todos os contextos.

Nota: acontece fortemente - antes exclui operações de consumo.

Portanto, uma operação de liberação / consumo não pode ser Strongly-HB.

Liberar / adquirir pode ser HB e Simply-HB (porque liberar / adquirir sincroniza com), mas não é necessariamente Strongly-HB. Porque o Strongly-HB diz especificamente que A deve sincronizar-se com B E ser uma operação Sequencialmente Consistente.

                            Is happens-before guaranteed?

                        HB             Simply-HB          Strongly-HB

relaxed                 no                 no                 no
release/consume        yes                 no                 no      
release/acquire        yes                yes                 no
S.C.                   yes                yes                yes

O que significa "A parece ser avaliado antes de B em todos os contextos" na nota?

Todos os contextos: todos os threads / todas as CPUs veem (ou "eventualmente concordarão") a mesma ordem. Esta é a garantia de consistência sequencial - uma ordem global de modificação total de todas as variáveis. As cadeias de aquisição / liberação garantem apenas a ordem de modificação percebida para os segmentos que participam da cadeia. Tópicos fora da cadeia são teoricamente permitidos para ver uma ordem diferente.

Não sei por que o Strongly-HB e o Simply-HB foram introduzidos. Talvez para ajudar a esclarecer como operar em torno de consumir? O Strongly-HB possui boas propriedades - se um thread observa A acontece fortemente antes de B, ele sabe que todos os threads irão observar a mesma coisa.

A história do consumo:

Paul E. McKenney é responsável por consumir os padrões C e C ++. Consuma garantias ordenando entre a atribuição do ponteiro e a memória para a qual aponta. Foi inventado por causa do DEC Alpha. O DEC Alpha poderia desreferenciar especulativamente um ponteiro, portanto, também tinha uma cerca de memória para evitar isso. O DEC Alpha não é mais fabricado e nenhum processador hoje possui esse comportamento. Consumir se destina a ser muito relaxado.

Humphrey Winnebago
fonte
11
Minha nossa. Eu quase me arrependo de fazer essa pergunta. Quero voltar a resolver os problemas fáceis do C ++, como as regras de invalidação do iterador, pesquisa de nome dependente de argumento, operadores de conversão definidos pelo usuário do modelo, dedução de argumento do modelo, quando a pesquisa de nome estiver em uma classe base no membro de um modelo e quando você pode converter em uma base virtual no início da construção de um objeto.
curiousguy
Re: consumir. Você afirma que o destino da ordem de consumo está vinculado ao destino do DEC Alpha e não tem valor fora desse arco específico?
arquivo
11
Esta é uma boa pergunta. Olhando mais agora, parece que o consumo poderia teoricamente dar um aumento no desempenho de arcos fracamente ordenados como ARM e PowerPC. Dê-me mais tempo para investigar.
Humphrey Winnebago
11
Eu diria que o consumo existe por causa de todos os ISAs fracamente ordenados, exceto o Alpha. No Alpha asm, as únicas opções são relaxadas e adquirem (e seq-cst), não a ordem de dependência. mo_consumedestina-se a tirar proveito da ordem de dependência de dados em CPUs reais e formalizar que o compilador não pode interromper a dependência de dados via previsão de ramificação. por exemplo, int *p = load(); tmp = *p;poderia ser interrompido pela introdução do compilador if(p==known_address) tmp = *known_address; else tmp=*p;se houvesse algum motivo para esperar que um determinado valor de ponteiro fosse comum. Isso é legal para relaxar, mas não para consumir.
Peter Cordes
@ PeterCordes certo ... arcos com pedidos fracos precisam emitir uma barreira de memória para aquisição, mas (teoricamente) não para consumo. Parece que você acha que se o Alfa nunca existisse, ainda teríamos consumido? Além disso, você está basicamente dizendo que consumir é uma barreira do compilador sofisticada (ou "padrão").
Humphrey Winnebago