Qual é o custo da operação atômica (qualquer um de comparar e trocar ou adicionar / decrementar atômico)? Quantos ciclos ele consome? Ele pausará outros processadores no SMP ou NUMA ou bloqueará os acessos à memória? Ele irá liberar o buffer de reordenamento na CPU fora de ordem?
Quais efeitos estarão no cache?
Estou interessado em CPUs modernas e populares: x86, x86_64, PowerPC, SPARC, Itanium.
Respostas:
Procurei dados reais nos últimos dias e não encontrei nada. No entanto, fiz algumas pesquisas que comparam o custo das operações atômicas com os custos dos erros de cache.
O custo do prefixo x86 LOCK, (incluindo
lock cmpxchg
para CAS atômico), antes do PentiumPro (conforme descrito no documento), é um acesso à memória (como uma falha de cache), + interromper as operações de memória por outros processadores, + qualquer contenção com outros processadores tentando BLOQUEAR o ônibus. No entanto, desde o PentiumPro, para memória armazenável de Writeback normal (toda a memória com a qual um aplicativo lida, a menos que você converse diretamente com o hardware), em vez de bloquear todas as operações de memória, apenas a linha de cache relevante é bloqueada (com base no link na resposta de @osgx ) .ou seja, o núcleo atrasa o atendimento das solicitações de compartilhamento MESI e RFO para a linha até depois da parte de armazenamento da
lock
operação real ed. Isso é chamado de "bloqueio de cache" e afeta apenas aquela linha de cache. Outros núcleos podem carregar / armazenar ou até mesmo fazer o CASing de outras linhas ao mesmo tempo.Na verdade, o caso do CAS pode ser mais complicado, conforme explicado nesta página , sem intervalos, mas com uma descrição criteriosa feita por um engenheiro confiável. (Pelo menos para o caso de uso normal em que você faz um carregamento puro antes do CAS real.)
Antes de entrar em muitos detalhes, direi que uma operação LOCKed custa um cache miss + a possível contenção com outro processador no mesmo cache, enquanto CAS + a carga anterior (que quase sempre é necessária, exceto em mutexes, onde você sempre CAS 0 e 1) podem custar dois erros de cache.
Ele explica que uma carga + CAS em um único local pode, na verdade, custar duas perdas de cache, como Load-Linked / Store-Conditional (veja a última opção aqui). Sua explicação se baseia no conhecimento do protocolo de coerência do cache MESI . Ele usa 4 estados para um cacheline: M (odificado), E (xclusivo), S (hared), I (nválido) (e portanto é chamado de MESI), explicado abaixo quando necessário. O cenário, explicado, é o seguinte:
Em todos os casos, uma solicitação de cacheline pode ser interrompida por outros processadores já modificando os dados.
fonte
Fiz alguns perfis com a seguinte configuração: A máquina de teste (AMD Athlon64 x2 3800+) foi inicializada, alternada para o modo longo (interrupções desabilitadas) e a instrução de interesse foi executada em um loop, 100 iterações desenroladas e 1.000 ciclos de loop. O corpo do loop foi alinhado a 16 bytes. O tempo foi medido com uma instrução rdtsc antes e depois do loop. Além disso, um loop fictício sem nenhuma instrução foi executado (que mediu 2 ciclos por iteração do loop e 14 ciclos para o resto) e o resultado foi subtraído do resultado do tempo de criação de perfil da instrução.
As seguintes instruções foram medidas:
lock cmpxchg [rsp - 8], rdx
" (com correspondência de comparação e incompatibilidade),lock xadd [rsp - 8], rdx
",lock bts qword ptr [rsp - 8], 1
"Em todos os casos, o tempo medido foi de cerca de 310 ciclos, o erro foi de cerca de +/- 8 ciclos
Este é o valor para execução repetida na mesma memória (em cache). Com uma falha de cache adicional, os tempos são consideravelmente maiores. Além disso, isso foi feito com apenas um dos 2 núcleos ativos, portanto, o cache era de propriedade exclusiva e nenhuma sincronização de cache era necessária.
Para avaliar o custo de uma instrução bloqueada em uma falha de cache, adicionei uma
wbinvld
instrução antes da instrução bloqueada e coloqueiwbinvld
mais umadd [rsp - 8], rax
no loop de comparação. Em ambos os casos, o custo foi de cerca de 80.000 ciclos por par de instruções! No caso de lock bts, a diferença de tempo era de cerca de 180 ciclos por instrução.Observe que essa é a taxa de transferência recíproca, mas como as operações bloqueadas são operações de serialização, provavelmente não há diferença na latência.
Conclusão: uma operação bloqueada é pesada, mas uma falha de cache pode ser muito mais pesada. Além disso: uma operação bloqueada não causa perda de cache. Ele só pode causar tráfego de sincronização de cache, quando um cacheline não é de propriedade exclusiva.
Para inicializar a máquina, usei uma versão x64 do FreeLdr do projeto ReactOS. Aqui está o código-fonte do ASM:
fonte
No SMP baseado em barramento, o prefixo atômico
LOCK
afirma (liga) um sinal de fio de barramentoLOCK#
. Isso proibirá outros cpus / dispositivos no barramento para usá-lo.Livro Ppro e P2 http://books.google.com/books?id=3gDmyIYvFH4C&pg=PA245&dq=lock+instruction+pentium&lr=&ei=_E61S5ehLI78zQSzrqwI&cd=1#v=onepage&q=lock%20instruction%20pentium&f=false pages 244-false
fonte
LOCK
não faz um bloqueio de barramento todas as vezes, a menos que os dados estejam desalinhados na linha do cache ou haja uma contenção do cache. Verifique rigtorp.se/split-locks para números atualizados.