multithreading sistemas Lock xchg tem o mesmo comportamento que mfence?



sistemas operacionais artigo (1)

O que eu quero saber é se o lock xchg terá um comportamento semelhante ao mfence da perspectiva de um thread acessando um local de memória que está sendo alterado (digamos aleatoriamente) por outros threads. Isso garante que eu recebo o valor mais atualizado? De memória ler / escrever instruções a seguir?

A razão da minha confusão é:

8.2.2 “As leituras ou gravações não podem ser reordenadas com instruções de E / S, instruções bloqueadas ou instruções de serialização.”

-Intel 64 Developers Manual vol. 3

Isso se aplica a threads?

mfence afirma:

Executa uma operação de serialização em todas as instruções de carregamento da memória e armazenamento na memória que foram emitidas antes da instrução MFENCE. Essa operação de serialização garante que todas as instruções de carregamento e armazenamento que precedem na ordem do programa a instrução MFENCE seja visível globalmente antes de qualquer instrução de carregamento ou armazenamento que siga a instrução MFENCE seja visível globalmente. A instrução MFENCE é solicitada com relação a todas as instruções de carregamento e armazenamento, outras instruções MFENCE, quaisquer instruções SFENCE e LFENCE e quaisquer instruções de serialização (como a instrução CPUID).

Intel 64 Manual do desenvolvedor Vol 3A

Isso soa como uma garantia mais forte. Como parece, o mfence está quase liberando o buffer de gravação ou, pelo menos, alcançando o buffer de gravação e outros núcleos para garantir que meus futuros carregamentos / lojas estejam atualizados.

Quando marcadas em bancada, as duas instruções ficam na ordem de ~ 100 ciclos para serem concluídas. Portanto, não vejo grande diferença de qualquer maneira.

Principalmente, estou apenas confuso. As instruções são baseadas em lock usados ​​em mutexes, mas não contêm barreiras de memória. Então eu vejo programação sem bloqueio que usa cercas de memória, mas sem travas. Entendo que o AMD64 tem um modelo de memória muito forte, mas valores obsoletos podem persistir no cache. Se o lock não se comporta como o mfence , como as mutexes ajudam a ver o valor mais recente?


Answer #1

Acredito que sua pergunta é a mesma que perguntar se mfence tem a mesma semântica de barreira que as instruções pré-fixadas em x86 ou se fornece menos 1 ou garantias adicionais em alguns casos.

Minha melhor resposta atual é que essa era a intenção da Intel e que a documentação ISA garante que as mfence e lock forneçam a mesma semântica de esgrima, mas que, devido à supervisão da implementação, o mfence realmente fornece semântica de esgrima mais forte em hardware recente (desde pelo menos Haswell) . Em particular, o mfence pode cercar uma carga não temporal subseqüente de uma região de memória do tipo WC, enquanto as instruções lock não.

Sabemos disso porque a Intel nos diz isso em erratas do processador, como HSD162 (Haswell) e SKL155 (Skylake), que nos dizem que instruções bloqueadas não impedem uma leitura não temporal subsequente da memória WC:

MOVNTDQA da memória WC pode passar instruções bloqueadas anteriores

Problema: Uma execução de (V) MOVNTDQA (instrução de carregamento de streaming) que carrega da memória WC (combinação de gravação) pode parecer passar uma instrução bloqueada anterior que acessa uma linha de cache diferente.

Implicação: o software que espera um bloqueio para cercar as instruções MOVNTDQA subsequentes (V) pode não funcionar corretamente.

Solução alternativa: nenhuma identificada. O software que depende de uma instrução bloqueada para cercar as execuções subseqüentes do (V) MOVNTDQA deve inserir uma instrução MFENCE entre a instrução bloqueada e a instrução (V) MOVNTDQA subsequente.

A partir disso, podemos determinar que (1) a Intel provavelmente pretendeu que as instruções bloqueadas cercassem o NT carregadas da memória do tipo WC, ou isso não seria uma errata 0,5 e (2) que as instruções bloqueadas realmente não fazem isso, e A Intel não conseguiu ou optou por não consertar isso com uma atualização de microcódigo, e recomenda-se o mfence .

No Skylake, o mfence realmente perdeu sua capacidade de esgrima adicional em relação às cargas do NT, conforme SKL079: MOVNTDQA da memória WC pode passar instruções anteriores do MFENCE - isso tem praticamente o mesmo texto que a errata da mfence lock , mas se aplica ao mfence . No entanto, o status dessa errata é "É possível que o BIOS contenha uma solução alternativa para essa errata.", Geralmente falada pela Intel para "uma atualização de microcódigo resolve isso".

Essa sequência de erratas talvez possa ser explicada pelo tempo: as erratas de Haswell só aparecem no início de 2016, anos após o lançamento desse processador, para que possamos assumir que o problema chamou a atenção da Intel durante um período moderado antes disso. Nesse ponto, Skylake quase certamente já estava em estado selvagem, com uma implementação aparentemente menos conservadora que também não mfence cargas de NT em regiões de memória do tipo WC. A correção da maneira como as instruções bloqueadas funcionam até Haswell provavelmente era impossível ou cara, com base em seu amplo uso, mas era necessário um meio para cercar as cargas do NT. aparentemente, o trabalho já havia sido feito em Haswell, e Skylake seria consertado para que mfence trabalhasse lá.

Na verdade, não explica por que o SKL079 (o mfence ) apareceu em janeiro de 2016, quase dois anos antes do SKL155 (o locked ) aparecer no final de 2017, ou por que o último apareceu tanto após a mesma errata de Haswell.

Pode-se especular sobre o que a Intel fará no futuro. Como eles não estavam dispostos / dispostos a alterar as instruções de lock de Haswell através da Skylake, representando centenas de milhões (bilhões?) De chips implantados, eles nunca serão capazes de garantir que as instruções bloqueadas cercem as cargas do NT, para que possam considerar fazer esse é o comportamento documentado e arquitetado no futuro. Ou eles podem atualizar as instruções bloqueadas, de modo a evitar tais leituras, mas, na prática, você não pode confiar nisso provavelmente por uma década ou mais, até que os chips com o atual comportamento de vedação estejam quase fora de circulação.

Semelhante ao Haswell, de acordo com BV116 e BJ138 , as cargas do NT podem passar instruções bloqueadas anteriores no Sandy Bridge e Ivy Bridge, respectivamente. É possível que as microarquiteturas anteriores também sofram desse problema. Esse "bug" parece não existir em Broadwell e nas microarquiteturas após Skylake.

Peter Cordes escreveu um pouco sobre a mudança da cerca de Skylake no final desta resposta .

A parte restante desta resposta é minha resposta original, antes que eu soubesse da errata e que é deixada principalmente para interesse histórico.

Resposta antiga

Meu palpite informado sobre a resposta é que o mfence fornece funcionalidade de barreira adicional: entre acessos usando instruções mfence ordenadas (por exemplo, lojas NT) e talvez entre acessos em regiões mal ordenadas (por exemplo, memória do tipo WC).

Dito isto, este é apenas um palpite informado e você encontrará detalhes de minha investigação abaixo.

Detalhes

Documentação

Não está exatamente claro até que ponto os efeitos de consistência de memória do mfence diferem dos fornecidos pelas instruções pré-fixadas por lock (incluindo xchg com um operando de memória, que está implicitamente bloqueado).

Eu acho que é seguro dizer que somente com relação às regiões de memória de write-back e não envolvendo acessos não temporais, o mfence fornece a mesma semântica de pedidos que a operação com lock pré-fixado.

O que está aberto para debate é se o mfence difere de alguma maneira das instruções com prefixo lock quando se trata de cenários fora dos itens acima, especialmente quando os acessos envolvem outras regiões que não as regiões WB ou quando operações não temporais (streaming) estão envolvidas.

Por exemplo, você pode encontrar algumas sugestões (como here ou here ) de que o mfence implica em forte semântica de barreira quando operações do tipo WC (por exemplo, armazenamentos do NT) estão envolvidas.

Por exemplo, citando o Dr. McCalpin neste tópico (ênfase adicionada):

A instrução fence só é necessária para ter certeza absoluta de que todos os armazenamentos não temporais são visíveis antes de um repositório "comum" subsequente. O caso mais óbvio em que isso importa é em um código paralelo, em que a "barreira" no final de uma região paralela pode incluir um armazenamento "comum". Sem uma cerca, o processador ainda pode ter modificado os dados nos buffers de combinação de gravação, mas passa pela barreira e permite que outros processadores leiam cópias "antigas" dos dados combinados de gravação. Esse cenário também pode se aplicar a um único encadeamento migrado pelo sistema operacional de um núcleo para outro (sem ter certeza sobre esse caso).

Não me lembro do raciocínio detalhado (ainda não há café suficiente esta manhã), mas a instrução que você deseja usar após as lojas não temporais é uma MFENCE. De acordo com a Seção 8.2.5 do Volume 3 do SWDM, o MFENCE é a única instrução de vedação que impede que cargas subsequentes e armazenamentos subsequentes sejam executados antes da conclusão da vedação. Surpreende-me que isso não seja mencionado na Seção 11.3.1, que mostra como é importante garantir manualmente a coerência ao usar a combinação de gravação, mas não mostra como fazê-lo!

Vamos verificar a seção 8.2.5 do SDM da Intel:

Fortalecendo ou enfraquecendo o modelo de pedido de memória

As arquiteturas Intel 64 e IA-32 fornecem vários mecanismos para fortalecer ou enfraquecer o modelo de pedido de memória para lidar com situações especiais de programação. Esses mecanismos incluem:

• As instruções de E / S, as instruções de bloqueio, o prefixo LOCK e as instruções de serialização forçam pedidos mais fortes no processador.

• As instruções SFENCE (introduzidas na arquitetura IA-32 no processador Pentium III) e as instruções LFENCE e MFENCE (introduzidas no processador Pentium 4) fornecem recursos de pedido e serialização de memória para tipos específicos de operações de memória.

Esses mecanismos podem ser usados ​​da seguinte maneira:

Os dispositivos mapeados na memória e outros dispositivos de E / S no barramento geralmente são sensíveis à ordem das gravações nos buffers de E / S. As instruções de E / S podem ser usadas para (as instruções IN e OUT) imporem uma forte ordem de gravação nos acessos da seguinte maneira. Antes de executar uma instrução de E / S, o processador espera que todas as instruções anteriores do programa sejam concluídas e que todas as gravações em buffer sejam drenadas para a memória. Somente busca de instruções e passeios pelas tabelas de páginas podem passar instruções de E / S. A execução das instruções subseqüentes não começa até que o processador determine que a instrução de E / S foi concluída.

Mecanismos de sincronização em sistemas com vários processadores podem depender de um forte modelo de pedido de memória. Aqui, um programa pode usar uma instrução de bloqueio, como a instrução XCHG ou o prefixo LOCK, para garantir que uma operação de leitura-modificação-gravação na memória seja executada atomicamente. As operações de bloqueio normalmente operam como operações de E / S, na medida em que esperam que todas as instruções anteriores sejam concluídas e que todas as gravações em buffer sejam drenadas para a memória (consulte a Seção 8.1.2, “Bloqueio de barramento”).

A sincronização do programa também pode ser realizada com instruções de serialização (consulte a Seção 8.3). Essas instruções são normalmente usadas em limites de tarefas ou procedimentos críticos para forçar a conclusão de todas as instruções anteriores antes que ocorra um salto para uma nova seção de código ou uma troca de contexto. Como as instruções de E / S e bloqueio, o processador aguarda até que todas as instruções anteriores sejam concluídas e todas as gravações em buffer tenham sido drenadas para a memória antes de executar a instrução de serialização.

As instruções SFENCE, LFENCE e MFENCE fornecem uma maneira com desempenho eficiente de garantir o carregamento e armazenamento de pedidos de memória entre rotinas que produzem resultados com ordem fraca e rotinas que consomem esses dados . As funções destas instruções são as seguintes:

• SFENCE - Serializa todas as operações de armazenamento (gravação) que ocorreram antes da instrução SFENCE no fluxo de instruções do programa, mas não afeta as operações de carregamento.

• LFENCE - Serializa todas as operações de carregamento (leitura) que ocorreram antes da instrução LFENCE no fluxo de instruções do programa, mas não afeta as operações de armazenamento.

• MFENCE - Serializa todas as operações de armazenamento e carregamento que ocorreram antes da instrução MFENCE no fluxo de instruções do programa.

Observe que as instruções SFENCE, LFENCE e MFENCE fornecem um método mais eficiente de controlar o pedido de memória do que a instrução CPUID.

Ao contrário da interpretação do Dr. McCalpin 2 , considero esta seção um tanto ambígua quanto ao fato de a mfence fazer algo extra. As três seções referentes a E / S, instruções bloqueadas e instruções de serialização implicam que elas fornecem uma barreira completa entre as operações de memória antes e depois da operação. Eles não abrem nenhuma exceção para memória com ordem fraca e, no caso das instruções de E / S, também se supõe que eles precisam trabalhar de maneira consistente com regiões de memória com ordem fraca, pois essas são frequentemente usadas para E / S.

Em seguida, a seção para as instruções FENCE , menciona explicitamente regiões fracas de memória: "As instruções SFENCE, LFENCE e MFENCE ** fornecem uma maneira com desempenho eficiente de garantir o carregamento e armazenamento de pedidos de memória entre rotinas que produzem resultados e rotinas com ordem fraca. consumir esses dados ".

Lemos nas entrelinhas e entendemos que essas são as únicas instruções que realizam isso e que as técnicas mencionadas anteriormente (incluindo instruções bloqueadas) não ajudam em regiões fracas da memória? Podemos encontrar algum suporte para essa ideia, observando que as instruções de vedação foram introduzidas 3 ao mesmo tempo que as instruções de loja não temporal com ordem fraca e por texto como o encontrado em 11.6.13 Instruções de dica de cacheabilidade que tratam especificamente de instruções com ordem fraca:

O grau em que um consumidor de dados sabe que os dados são fracamente ordenados pode variar para esses casos. Como resultado, a instrução SFENCE ou MFENCE deve ser usada para garantir a ordem entre rotinas que produzem dados com ordem fraca e rotinas que consomem os dados. O SFENCE e o MFENCE fornecem uma maneira com desempenho eficiente para garantir pedidos, garantindo que todas as instruções de loja que precedem o SFENCE / MFENCE na ordem do programa sejam visíveis globalmente antes de uma instrução de loja que siga a barreira.

Novamente, aqui as instruções da cerca são especificamente mencionadas como apropriadas para cercar instruções com ordens fracas.

Também encontramos suporte para a ideia de que a instrução bloqueada pode não fornecer uma barreira entre acessos pouco ordenados da última frase já citada acima:

Observe que as instruções SFENCE, LFENCE e MFENCE fornecem um método mais eficiente de controlar o pedido de memória do que a instrução CPUID.

Aqui está basicamente implícito que as instruções FENCE substituem essencialmente uma funcionalidade oferecida anteriormente pelo cpuid serialização em termos de pedido de memória. No entanto, se as instruções pré-fixadas com lock fornecerem a mesma capacidade de barreira que a cpuid , essa provavelmente seria a maneira sugerida anteriormente, uma vez que geralmente são muito mais rápidas que a cpuid que geralmente leva 200 ou mais ciclos. A implicação é que havia cenários (provavelmente com ordens fracamente ordenadas) que as instruções pré-fixadas por lock tratavam, e onde o cpuid estava sendo usado, e onde mfence agora é sugerido como uma substituição, implicando uma semântica de barreira mais forte do que as instruções pré-fixadas.

No entanto, podemos interpretar algumas das opções acima de uma maneira diferente: observe que, no contexto das instruções da cerca, é freqüentemente mencionado que elas são uma maneira de desempenho eficiente para garantir pedidos. Portanto, essas instruções não devem fornecer barreiras adicionais, mas simplesmente barreiras mais eficientes.

De fato, a sfence em alguns ciclos é muito mais rápida do que as instruções de serialização, como as instruções cpuid ou pré-fixadas por lock que geralmente são 20 ciclos ou mais. Por outro lado, o mfence geralmente não é mais rápido que as instruções bloqueadas 4 , pelo menos no hardware moderno. Ainda assim, poderia ter sido mais rápido quando introduzido, ou em algum projeto futuro, ou talvez fosse esperado que fosse mais rápido, mas isso não deu certo.

Portanto, não posso fazer uma determinada avaliação com base nessas seções do manual: acho que você pode argumentar razoavelmente que poderia ser interpretado de qualquer maneira.

Podemos examinar mais a documentação para obter várias instruções de armazenamento não temporal no guia Intel ISA. Por exemplo, na documentação do movnti armazenamento não temporal, você encontra a seguinte citação:

Como o protocolo WC usa um modelo de consistência de memória com ordem fraca, uma operação de vedação implementada com a instrução SFENCE ou MFENCE deve ser usada em conjunto com as instruções MOVNTI se vários processadores puderem usar diferentes tipos de memória para ler / gravar os locais de memória de destino.

A parte sobre "se vários processadores podem usar diferentes tipos de memória para ler / gravar os locais de memória de destino" é um pouco confusa para mim. Eu esperaria que isso dissesse algo como "impor a ordem na ordem de gravação globalmente visível entre instruções usando dicas fracamente ordenadas" ou algo assim. De fato, o tipo de memória real (por exemplo, conforme definido pelo MTTR) provavelmente nem entra em jogo aqui: os problemas de pedidos podem surgir apenas na memória WB quando se usa instruções mal ordenadas.

atuação

É relatado que a instrução mfence leva 33 ciclos (latência consecutiva) em CPUs modernas, com base no tempo de instrução do nevoeiro Agner, mas um instrumento bloqueado mais complexo, como lock cmpxchg leva apenas 18 ciclos.

Se o mfence forneceu semântica de barreira não mais forte que o lock cmpxchg , o último está fazendo um trabalho estritamente mais e não há motivo aparente para o mfence demorar significativamente mais . Claro que você poderia argumentar que o lock cmpxchg é simplesmente mais importante que o mfence e, portanto, obtém mais otimização. Esse argumento é enfraquecido pelo fato de que todas as instruções bloqueadas são consideravelmente mais rápidas do que as mfence , mesmo as usadas com pouca frequência. Além disso, você poderia imaginar que, se houvesse uma implementação de barreira única compartilhada por todas as instruções de lock , o mfence simplesmente usaria a mesma, já que é a validação mais simples e fácil.

Portanto, o desempenho mais lento do mfence é, na minha opinião, uma evidência significativa de que o mfence está fazendo algum extra .

0.5 Este não é um argumento impermeável. Algumas coisas podem aparecer em erratas que aparentemente são "projetadas" e não um bug, como a dependência falsa popcnt no registro de destino - portanto, algumas erratas podem ser consideradas uma forma de documentação para atualizar as expectativas, em vez de sempre implicar em um bug de hardware.

1 Evidentemente, a instrução pré-fixada por lock também executa uma operação atômica que não é possível obter apenas com o mfence , portanto, as instruções pré-fixadas por lock definitivamente possuem funcionalidade adicional. Portanto, para que o mfence seja útil, esperamos que ele tenha semântica de barreira adicional em alguns cenários ou que tenha um desempenho melhor.

2 Também é inteiramente possível que ele estivesse lendo uma versão diferente do manual, onde a prosa era diferente.

3 SFENCE no SSE, lfence e mfence no SSE2.

4 E muitas vezes é mais lento: a Agner está listada com latência de 33 ciclos em hardware recente, enquanto as instruções bloqueadas costumam ter cerca de 20 ciclos.





memory-barriers