A explicação de uma ordem relaxada é errônea na preferência?


13

Na documentação de std::memory_orderem cppreference.com, há um exemplo de pedido relaxado:

Ordenação descontraída

As operações atômicas marcadas memory_order_relaxednão são operações de sincronização; eles não impõem uma ordem entre acessos simultâneos à memória. Eles garantem apenas consistência de ordem de atomicidade e modificação.

Por exemplo, com xey inicialmente zero,

// Thread 1:
r1 = y.load(std::memory_order_relaxed); // A
x.store(r1, std::memory_order_relaxed); // B
// Thread 2:
r2 = x.load(std::memory_order_relaxed); // C
y.store(42, std::memory_order_relaxed); // D

é permitido produzir r1 == r2 == 42 porque, embora A seja sequenciado antes de B no segmento 1 e C seja sequenciado antes de D no segmento 2, nada impede que D apareça antes de A na ordem de modificação de y e B de aparecendo antes de C na ordem de modificação de x. O efeito colateral de D em y pode ser visível para a carga A no encadeamento 1, enquanto o efeito colateral de B em x pode ser visível na carga C no encadeamento 2. Em particular, isso pode ocorrer se D for concluído antes de C em segmento 2, devido a reordenação do compilador ou em tempo de execução.

diz "C é sequenciado antes de D no segmento 2".

De acordo com a definição de sequenciado antes, que pode ser encontrado em Ordem de avaliação , se A for sequenciado antes de B, a avaliação de A será concluída antes do início da avaliação de B. Como C é sequenciado antes de D no encadeamento 2, C deve ser concluído antes do início de D, portanto, a parte da condição da última frase do instantâneo nunca será satisfeita.


Sua pergunta é especificamente sobre C ++ 11?
curiousguy 13/01

não, isso também se aplica a c ++ 14,17. Sei que o compilador e a CPU podem reordenar C com D.Mas se a reordenação ocorrer, C não poderá ser concluído antes que D comece. Então, acho que há um uso indevido da terminologia na frase "A é sequenciada - antes de B no segmento 1 e C é sequenciada antes de D no segmento 2". É mais preciso dizer "No código, A é COLOCADO ANTES de B no segmento 1 e C é COLOCADO ANTES de D no segmento 2". O objetivo desta pergunta é confirmar este pensamento
abigaile 13/01

Nada é definido em termos de "reordenação".
curiousguy

Respostas:


13

Eu acredito que a preferência é certa. Eu acho que isso se resume à regra "como se" [introdução.execução] / 1 . Compiladores são obrigados a reproduzir apenas o comportamento observável do programa descrito por seu código. Uma relação sequenciada antes é estabelecida apenas entre as avaliações da perspectiva do segmento em que essas avaliações são realizadas [introdução.execução] / 15 . Isso significa que quando duas avaliações sequenciadas uma após a outra aparecem em algum lugar de algum encadeamento, o código realmente em execução nesse encadeamento deve se comportar como se o que quer que a primeira avaliação fizesse realmente afetar o que a segunda avaliação faz. Por exemplo

int x = 0;
x = 42;
std::cout << x;

deve imprimir 42. No entanto, o compilador não precisa realmente armazenar o valor 42 em um objeto xantes de ler o valor novamente desse objeto para imprimi-lo. É bom lembrar que o último valor a ser armazenado xfoi 42 e, em seguida, basta imprimir o valor 42 diretamente antes de fazer um armazenamento real do valor 42 em x. De fato, se xfor uma variável local, ela também pode apenas rastrear qual valor foi atribuído pela última vez a qualquer momento e nunca criar um objeto ou realmente armazenar o valor 42. Não há como o thread diferenciar. O comportamento sempre será como se houvesse uma variável e como se o valor 42 estivesse realmente armazenado em um objeto x antessendo carregado desse objeto. Mas isso não significa que o código de máquina gerado tenha que realmente armazenar e carregar qualquer coisa em qualquer lugar. Tudo o que é necessário é que o comportamento observável do código de máquina gerado seja indistinguível do que seria o comportamento se todas essas coisas realmente acontecessem.

Se olharmos para

r2 = x.load(std::memory_order_relaxed); // C
y.store(42, std::memory_order_relaxed); // D

então sim, C é sequenciado antes de D. Mas quando visto a partir desse encadeamento isoladamente, nada que C faça afeta o resultado de D. E nada que D faça alteraria o resultado de C. A única maneira de um afetar o outro seria como uma conseqüência indireta de algo acontecendo em outro segmento. No entanto, especificando std::memory_order_relaxed, você declarou explicitamenteque a ordem na qual a carga e a loja são observadas por outro encadeamento é irrelevante. Como nenhum outro encadeamento pode observar a carga e o armazenamento em qualquer ordem específica, não há nada que outro encadeamento possa fazer para que C e D se afetem de maneira consistente. Portanto, a ordem na qual a carga e a loja são realmente executadas é irrelevante. Assim, o compilador é livre para reordená-los. E, como mencionado na explicação abaixo desse exemplo, se o armazenamento de D for realizado antes da carga de C, então r1 == r2 == 42 pode realmente acontecer…


Então, essencialmente, o padrão afirma que C deve acontecer antes de D , mas o compilador acredita que não pode ser provado se C ou D aconteceu a seguir e, devido à regra como se, os reordena de qualquer maneira, correto?
Fureeish 11/01

4
@Fureeish No. C deve acontecer antes de D, na medida em que o encadeamento no qual eles acontecem pode dizer. Observar de outro contexto pode ser inconsistente com essa visão.
Deduplicator

5
@curiousguy Esta afirmação parece semelhante aos outros evangelismos anteriores em C ++ .
Lightness Races em órbita

11
@curiousguy Michael publicou uma longa explicação sobre isso, juntamente com links para os capítulos relevantes do padrão.
Lightness Races em órbita em

2
@curiousguy A norma faz uma etiqueta de seu provisões "como-se a regra" em uma nota de rodapé: "Esta disposição é às vezes chamado de‘como se’regra" intro.execution
Caleth

1

Às vezes, é possível que uma ação seja ordenada em relação a duas outras seqüências de ações, sem implicar em nenhuma ordem relativa das ações nessas seqüências em relação uma à outra.

Suponha, por exemplo, que um tenha os três eventos a seguir:

  • armazenar 1 a p1
  • carregar p2 em temp
  • armazenar 2 para p3

e a leitura de p2 é ordenada independentemente após a gravação de p1 e antes da gravação de p3, mas não há uma ordem específica na qual ambos p1 e p3 participam. Dependendo do que é feito com p2, pode ser impraticável para um compilador adiar p1 além de p3 e ainda atingir a semântica necessária com p2. Suponha, no entanto, que o compilador soubesse que o código acima fazia parte de uma sequência maior:

  • armazenar 1 a p2 [sequenciado antes da carga de p2]
  • [faça o acima]
  • loja 3 em p1 [sequenciada após a outra loja para p1]

Nesse caso, ele poderia determinar que poderia reordenar o armazenamento para p1 após o código acima e consolidá-lo com o seguinte armazenamento, resultando em código que grava p3 sem escrever p1 primeiro:

  • definir temp para 1
  • armazenar temp para p2
  • armazenar 2 para p3
  • armazenar 3 para p1

Embora possa parecer que as dependências de dados possam fazer com que certas partes das relações de sequenciamento se comportem de forma transitória, um compilador pode identificar situações em que não existem dependências aparentes de dados e, portanto, não teria os efeitos transitivos que se esperaria.


1

Se houver duas instruções, o compilador gerará código em ordem seqüencial, portanto, o código para a primeira será colocado antes da segunda. Mas a cpus possui internamente pipelines e é capaz de executar operações de montagem em paralelo. A instrução C é uma instrução de carregamento. Enquanto a memória está sendo buscada, o pipeline processará as próximas instruções e, como elas não dependem da instrução de carregamento, elas poderão ser executadas antes da conclusão de C (por exemplo, dados para D estavam no cache, C na memória principal).

Se o usuário realmente precisou que as duas instruções fossem executadas seqüencialmente, operações mais rigorosas de ordenação de memória podem ser usadas. Em geral, os usuários não se importam desde que o programa esteja logicamente correto.


-9

Tudo o que você pensa é igualmente válido. O padrão não diz o que é executado sequencialmente, o que não é e como ele pode ser misturado .

Cabe a você e a todos os programadores criar uma semântica consistente sobre essa bagunça, um trabalho digno de vários doutores.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.