Depois de ler The JSR-133 Cookbook for Compiler Writers sobre a implementação de volátil, especialmente a seção "Interações com instruções atômicas", presumo que ler uma variável volátil sem atualizá-la precisa de um LoadLoad ou uma barreira LoadStore. Mais abaixo na página, vejo que LoadLoad e LoadStore são efetivamente autônomos em CPUs X86. Isso significa que as operações de leitura volátil podem ser feitas sem uma invalidação de cache explícita no x86 e são tão rápidas quanto uma leitura de variável normal (desconsiderando as restrições de reordenamento de volátil)?
Acho que não entendi direito. Alguém poderia se importar em me esclarecer?
EDIT: Gostaria de saber se existem diferenças em ambientes multiprocessadores. Em sistemas de CPU única, a CPU pode olhar para seus próprios caches de thread, como afirma John V., mas em sistemas com várias CPUs deve haver alguma opção de configuração para as CPUs de que isso não é suficiente e a memória principal deve ser atingida, tornando o volátil mais lento em sistemas multi cpu, certo?
PS: No meu caminho para aprender mais sobre isso, tropecei nos seguintes ótimos artigos e, como esta questão pode ser interessante para outras pessoas, compartilharei meus links aqui:
Respostas:
Na Intel, uma leitura volátil não contestada é bastante barata. Se considerarmos o seguinte caso simples:
Usando a capacidade do Java 7 de imprimir código assembly, o método run é semelhante a:
Se você olhar as 2 referências para getstatic, a primeira envolve uma carga da memória, a segunda pula a carga, pois o valor é reutilizado do (s) registro (s) em que já está carregado (o comprimento é de 64 bits e no meu laptop de 32 bits usa 2 registros).
Se tornarmos a variável l volátil, a montagem resultante será diferente.
Nesse caso, ambas as referências getstatic para a variável l envolvem uma carga da memória, ou seja, o valor não pode ser mantido em um registro em várias leituras voláteis. Para garantir que haja uma leitura atômica, o valor é lido da memória principal em um registro MMX,
movsd 0x6fb7b2f0(%ebp),%xmm0
tornando a operação de leitura uma única instrução (no exemplo anterior, vimos que o valor de 64 bits normalmente exigiria duas leituras de 32 bits em um sistema de 32 bits).Portanto, o custo geral de uma leitura volátil será aproximadamente equivalente a uma carga de memória e pode ser tão barato quanto um acesso de cache L1. No entanto, se outro núcleo estiver gravando na variável volátil, o cache-line será invalidado, exigindo uma memória principal ou talvez um acesso ao cache L3. O custo real dependerá muito da arquitetura da CPU. Mesmo entre Intel e AMD, os protocolos de coerência de cache são diferentes.
fonte
De modo geral, na maioria dos processadores modernos, uma carga volátil é comparável a uma carga normal. Um armazenamento volátil é cerca de 1/3 do tempo de uma entrada / saída do monitor. Isso é visto em sistemas que são coerentes com o cache.
Para responder à pergunta do OP, as gravações voláteis são caras, enquanto as leituras geralmente não são.
Sim, às vezes, ao validar um campo, a CPU pode nem mesmo atingir a memória principal, em vez disso, espiar outros caches de thread e obter o valor de lá (explicação muito geral).
No entanto, apoio a sugestão de Neil de que, se você tiver um campo acessado por vários threads, deve envolvê-lo como AtomicReference. Por ser um AtomicReference, ele executa aproximadamente a mesma taxa de transferência para leituras / gravações, mas também é mais óbvio que o campo será acessado e modificado por vários threads.
Edite para responder à edição do OP:
A coerência do cache é um protocolo um pouco complicado, mas em resumo: as CPUs compartilharão uma linha de cache comum que é anexada à memória principal. Se uma CPU carregar memória e nenhuma outra CPU a tiver, a CPU assumirá que é o valor mais atualizado. Se outra CPU tentar carregar o mesmo local de memória, a CPU já carregada estará ciente disso e realmente compartilhará a referência em cache com a CPU solicitante - agora a CPU solicitante tem uma cópia dessa memória em seu cache de CPU. (Nunca foi necessário procurar na memória principal a referência)
Há um pouco mais de protocolo envolvido, mas isso dá uma ideia do que está acontecendo. Também para responder à sua outra pergunta, com a ausência de vários processadores, as leituras / gravações voláteis podem de fato ser mais rápidas do que com vários processadores. Existem alguns aplicativos que, na verdade, seriam executados com mais rapidez simultaneamente com uma única CPU do que com várias.
fonte
Nas palavras do modelo de memória Java (conforme definido para Java 5+ em JSR 133), qualquer operação - leitura ou gravação - em uma
volatile
variável cria um relacionamento acontece antes em relação a qualquer outra operação na mesma variável. Isso significa que o compilador e o JIT são forçados a evitar certas otimizações, como instruções de reordenação no thread ou execução de operações apenas no cache local.Como algumas otimizações não estão disponíveis, o código resultante é necessariamente mais lento do que deveria, embora provavelmente não muito.
No entanto, você não deve fazer uma variável
volatile
menos que saiba que ela será acessada de vários threads fora dossynchronized
blocos. Mesmo assim, você deve considerar se volátil é a melhor escolha contrasynchronized
,AtomicReference
e seus amigos, asLock
classes explícitas , etc.fonte
O acesso a uma variável volátil é, em muitos aspectos, semelhante a agrupar o acesso a uma variável comum em um bloco sincronizado. Por exemplo, o acesso a uma variável volátil impede que a CPU reordene as instruções antes e depois do acesso, e isso geralmente retarda a execução (embora eu não possa dizer quanto).
De forma mais geral, em um sistema multiprocessador, não vejo como o acesso a uma variável volátil pode ser feito sem penalidade - deve haver alguma maneira de garantir que uma gravação no processador A seja sincronizada com uma leitura no processador B.
fonte