Por que o estado compartilhado prejudica o desempenho?

19

Eu tenho trabalhado sob o princípio de compartilhar nada da programação simultânea. Essencialmente, todos os meus threads de trabalho têm cópias imutáveis ​​de somente leitura do mesmo estado que nunca são compartilhadas entre eles ( mesmo por referência ). De um modo geral, isso funcionou muito bem.

Agora, alguém introduziu um cache singleton sem bloqueio ( por exemplo, um dicionário estático ) que todos os threads estão acessando simultaneamente. Como o dicionário nunca é alterado após a inicialização, não há bloqueios. Não houve nenhum problema de segurança de thread, mas agora há uma degradação do desempenho.

A questão é ... como não há bloqueios, por que a introdução desse singleton cria um impacto no desempenho? O que exatamente está acontecendo nos bastidores que poderia explicar isso?

Para confirmar, o acesso a esse novo singleton é a única alteração e posso recriá-lo com segurança simplesmente comentando a chamada para o cache.

JoeGeeky
fonte
8
Você apontou um criador de perfil para o código?
Timo Geusch
2
É improvável que o perfil responda a essa pergunta, a menos que você esteja criando o perfil do CLR e possivelmente do kernel do Windows (não é uma tarefa fácil para o programador médio).
precisa saber é o seguinte
1
@JoeGeeky Tudo bem então, acho que a única coisa a fazer por mim aqui é +1 e favoritar! Parece estranho, já que ambos estão no mesmo nível de engano, afinal, e deve caber no cache do processador de qualquer maneira, etc ...
Max
2
FWIT Eu criei alguns tópicos e corri alguns temporizadores. Instanciei uma classe, singleton, lockedSingleton e dict <string, string>. Após a primeira instanciação de cada uma, as execuções consecutivas levaram cerca de 2000ns para qualquer objeto. O dicionário correu 2x mais devagar, pode ser causado pelo código do construtor ... é mais lento que o bloqueio por si só. Considerando todo o GC, o manuseio do SO das filas de threads e outras despesas gerais ... não tenho certeza se alguém pode realmente responder a essa pergunta. Mas, pelos meus resultados, não acredito que o problema tenha a ver com Singletons. Não se for implementado como no MSDN.Exclui otimizações do compilador.
usar o seguinte
1
@JoeGeeky - outro pensamento: usar o cache adiciona um nível de indireção? Se acessado com frequência, procurar um deref de ponteiro extra (ou equivalente a MSIL) pode adicionar algum tempo ao longo de uma cópia local menos indireta.
Sdg

Respostas:

8

Pode ser que o estado imutável compartilhe uma linha de cache com algo mutável. Nesse caso, uma alteração no estado mutável próximo pode ter o efeito de forçar uma ressincronização dessa linha de cache entre os núcleos, o que pode diminuir o desempenho.

Aidan Cully
fonte
3
Isso soa como um false sharingcenário que você está descrevendo. Para isolar isso, preciso criar um perfil do cache L2. Infelizmente, esses são tipos de referência, portanto, adicionar espaço no buffer não será uma opção se é isso que está realmente acontecendo.
JoeGeeky
3

Eu me certificaria de que os métodos Equals()e GetHashCode()dos objetos que você usa como chaves do dicionário não tenham efeitos colaterais inesperados e não compatíveis com o encadeamento. A criação de perfil ajudaria bastante aqui.

Se, por um acaso, suas chaves são strings, talvez seja isso: existem rumores de que as strings se comportam como objetos imutáveis, mas, por causa de certas otimizações, elas são implementadas internamente de maneira mutável, com tudo o que isso implica em relação à multithreading .

Eu tentaria passar o dicionário para os segmentos que o usam como referência regular, em vez de um singleton, para verificar se o problema está no compartilhamento ou no singleton do dicionário. (Eliminando as possíveis causas.)

Eu também tentaria com um em ConcurrentDictionaryvez de um regular Dictionary, caso seu uso produza alguns resultados surpreendentes. Há muitas coisas a serem especuladas sobre o problema em questão, se um tiver um ConcurrentDictionarydesempenho muito melhor ou muito pior do que o normal Dictionary.

Se nenhuma das opções acima apontar para o problema, acho que o desempenho degradado é causado por algum tipo estranho de contenção entre o encadeamento de coleta de lixo e o restante de seus encadeamentos, pois o coletor de lixo está tentando descobrir se os objetos no seu dicionário precisam ser descartados ou não, enquanto estão sendo acessados ​​por seus threads.

Mike Nakis
fonte