Pergunta

O carregamento simples possui semântica de aquisição em x86, o armazenamento simples possui semântica de liberação, porém o compilador ainda pode reordenar instruções.Embora barreiras e instruções bloqueadas (xchg bloqueado, cmpxchg bloqueado) impeçam o reordenamento do hardware e do compilador, cargas e armazenamentos simples ainda são necessários para proteção com barreiras do compilador.Visual C++ fornece a função _ReadWriterBarrier(), que evita a reordenação do compilador, e C++ também fornece palavras-chave voláteis pelo mesmo motivo.Escrevo todas essas informações apenas para ter certeza de que entendi tudo certo.Portanto, tudo o que foi escrito acima é verdade. Existe algum motivo para marcar como variáveis ​​voláteis que serão usadas em funções protegidas com _ReadWriteBarrier()?

Por exemplo:

int load(int& var)
{
    _ReadWriteBarrier();
    T value = var;
    _ReadWriteBarrier();
    return value;
}

É seguro tornar essa variável não volátil?Pelo que entendi, porque a função está protegida e nenhuma reordenação pode ser feita pelo compilador interno.Por outro lado, o Visual C++ fornece um comportamento especial para variáveis ​​voláteis (diferente daquele padrão), faz leituras e gravações voláteis, cargas e armazenamentos atômicos, mas meu alvo é x86 e cargas e armazenamentos simples devem ser atômicos em x86 de qualquer maneira, certo?

Desde já, obrigado.

Foi útil?

Solução

A palavra-chave volátil também está disponível em C."volátil" é frequentemente usado em sistemas embarcados, especialmente quando o valor da variável pode mudar a qualquer momento - sem que nenhuma ação seja tomada pelo código - três cenários comuns incluem a leitura de um registro periférico mapeado na memória ou variáveis ​​globais modificadas por um interromper a rotina de serviço ou aqueles dentro de um programa multithread.

Portanto, é o último cenário em que volátil pode ser considerado semelhante a _ReadWriteBarrier.

_ReadWriteBarrier não é uma função - _ReadWriteBarrier não insere nenhuma instrução adicional e não impede que a CPU reorganize leituras e gravações - apenas evita que o compilador as reorganize._ReadWriteBarrier evita a reordenação do compilador.

MemoryBarrier evita a reordenação da CPU!

Um compilador normalmente reorganiza instruções...C++ não contém suporte integrado para programas multithread, portanto, o compilador assume que o código é de thread único ao reordenar o código.Com o MSVC, use _ReadWriteBarrier no código, para que o compilador não mova leituras e gravações nele.

Verifique este link para uma discussão mais detalhada sobre esses tópicoshttp://msdn.microsoft.com/en-us/library/ee418650(v=vs.85).aspx

Em relação ao seu trecho de código - você não precisa usar ReadWriteBarrier como uma primitiva SYNC - a primeira chamada para _ReadWriteBarrier não é necessária.

Ao usar ReadWriteBarrier você não precisa usar volátil

Você escreveu "faz leituras e gravações voláteis de cargas e armazenamentos atômicos" - não acho que seja correto dizer isso, atomicidade e volatilidade são diferentes.As operações atômicas são consideradas indivisíveis - ... http://www.yoda.arachsys.com/csharp/threads/volatility.shtml

Outras dicas

Observação:Não sou especialista neste assunto, algumas de minhas afirmações são "o que ouvi na internet", mas acho que ainda posso esclarecer alguns equívocos.

[editar] Em geral, eu confiaria nas especificidades da plataforma, como leituras atômicas x86 e falta de OOOX, apenas em otimizações locais isoladas que são protegidas por um #ifdef verificando a plataforma alvo, idealmente acompanhada por uma solução portátil no #else caminho.

Coisas a observar

  • atomicidade das operações de leitura/gravação
  • reordenação devido a otimizações do compilador (isso inclui uma ordem diferente vista por outro thread devido ao simples cache de registro)
  • execução fora de ordem na CPU

Possíveis equívocos

1. Pelo que entendi, porque a função está protegida e nenhuma reordenação pode ser feita pelo compilador interno.
[editar] Esclarecer:o _ReadWriteBarrier fornece proteção contra reordenação de instruções; no entanto, você precisa olhar além do escopo da função. _ReadWriteBarrier foi corrigido no VS 2010 para fazer isso, versões anteriores podem ser quebradas (dependendo das otimizações que elas realmente fazem).

A otimização não se limita às funções.Existem vários mecanismos (inlining automático, geração de código de tempo de link) que abrangem funções e até unidades de compilação (e podem fornecer otimizações muito mais significativas do que o cache de registro de escopo pequeno).

2. Visual C++ [...] faz leituras e gravações voláteis, cargas atômicas e armazenamentos,
Onde você encontrou aquilo? MSDN diz que além do padrão, colocará barreiras de memória em torno de leituras e gravações, sem garantia para leituras atômicas.

[editar] Observe que C#, Java, Delphi etc.têm modelos de memória diferentes e podem oferecer garantias diferentes.

3. cargas e armazenamentos simples deveriam ser atômicos em x86 de qualquer maneira, certo?
Não, eles não são.Leituras desalinhadas não são atômicas.Eles acontecer de ser atômico se estiverem bem alinhados - um fato no qual não confiaria, a menos que seja isolado e facilmente trocado.Caso contrário, sua "simplificação para x86" se tornará um bloqueio para esse alvo.

[editar] Leituras desalinhadas acontecem:

char * c = new char[sizeof(int)+1];
load(*(int *)c);      // allowed by standard to be unaligned
load(*(int *)(c+1));  // unaligned with most allocators

#pragma pack(push,1)
struct 
{
   char c;
   int  i;
} foo;
load(foo.i);         // caller said so
#pragma pack(pop)

É claro que tudo isso é acadêmico, se você lembrar que o parâmetro deve estar alinhado e você controla todo o código.Eu não escreveria mais esse código, porque muitas vezes fui mordido pela preguiça do passado.

4. O carregamento simples possui semântica de aquisição em x86, o armazenamento simples possui semântica de liberação
Não.Os processadores x86 não usam execução fora de ordem (ou melhor, nenhum OOOX visível - eu acho), mas isso não impede o otimizador de reordenar as instruções.

5. _ReadBarrier / _writeBarrier / _readWriteBarrier Faça toda a mágica que não - eles apenas impedem a reordenação pelo otimizador.O MSDN finalmente tornou isso um grande aviso ruim para VS2010, mas a informação aparentemente se aplica a versões anteriores também.


Agora, para sua pergunta.

Presumo que o objetivo do snippet seja passar qualquer variável N e carregá-la (atomicamente?) A escolha direta seria uma leitura interligada ou (no Visual C++ 2005 e posterior) uma leitura volátil.

Caso contrário, você precisaria de uma barreira para o compilador e para a CPU antes da leitura. No salão VC++, isso seria:

int load(int& var)
{   
  // force Optimizer to complete all memory writes:
  // (Note that this had issues before VC++ 2010)
   _WriteBarrier();    

  // force CPU to settle all pending read/writes, and not to start new ones:
   MemoryBarrier();

   // now, read.
   int value = var;    
   return value;
}

Não, isso _WriteBarrier tem um segundo aviso no MSDN:*Nas versões anteriores do compilador Visual C++, as funções _ReadWriteBarrier e _WriteBarrier eram aplicadas apenas localmente e não afetavam as funções na árvore de chamadas.Essas funções agora são aplicadas em toda a árvore de chamadas.*


EU ter esperança está correto.stackoverflowers, corrija-me se eu estiver errado.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top