Pergunta

Com que frequência você se encontra realmente usando spinlocks em seu código? Como é que é comum se deparar com uma situação onde usando um loop ocupado realmente supera o uso de bloqueios?
Pessoalmente, quando eu escrever algum tipo de código que requer segurança do thread, que tendem a referência com diferentes primitivas de sincronização e, na medida em que vai, parece que o uso de bloqueios dá um melhor desempenho do que usar spinlocks. Não importa o quão pouco tempo eu realmente manter o bloqueio, a quantidade de contenção que recebo quando se utiliza spinlocks é muito maior do que o valor que recebo de uso de bloqueios (claro, eu corro meus testes em uma máquina multiprocessador).

Eu percebo que é mais provável que se deparar com um spinlock em código "de baixo nível", mas eu estou interessado em saber se você achar que é útil, mesmo um tipo mais alto nível de programação?

Foi útil?

Solução

Depende do que você está fazendo. No código de aplicação geral, você vai querer evitar spinlocks.

Em coisas de baixo nível, onde você só vai manter o bloqueio por um par de instruções, e latência é importante, uma spinlock mat ser uma solução melhor do que um bloqueio. Mas esses casos são raros, especialmente no tipo de aplicações onde a C # é normalmente usado.

Outras dicas

Em C #, "bloqueia Spin" têm sido, na minha experiência, quase sempre pior do que tomar um lock -. É uma ocorrência rara onde bloqueios de rotação irá superar um bloqueio

No entanto, isso nem sempre é o caso. NET 4 é a adição de um System.Threading .SpinLock estrutura. Isso proporciona benefícios em situações onde um bloqueio é mantido por um tempo muito curto, e sendo agarrado repetidamente. De docs MSDN sobre Estruturas de Dados para Programação Paralela :

Em cenários onde se espera que a espera para o bloqueio para ser curto, ofertas SpinLock melhor desempenho do que outras formas de bloqueio.

fechaduras spin pode superar outros mecanismos de bloqueio nos casos em que você está fazendo algo como bloqueio através de uma árvore - se você está apenas tendo travas em cada nó para um muito, muito curto período de tempo, eles podem se realizar um tradicional bloquear. Corri para isso em um motor de renderização com uma atualização cena de vários segmentos, em um ponto -. Spin locks perfilado para bloqueio outperform com Monitor.Enter

Para o meu trabalho em tempo real, em particular com drivers de dispositivo, eu usei-los um pouco justo. Acontece que (quando passado eu cronometrado isso) à espera de um objeto sincronização como um semáforo amarrado a uma interrupção de hardware mastiga pelo menos 20 microsegundos, não importa quanto tempo realmente leva para a interrupção para ocorrer. Um único cheque de um registo hardware de memória mapeada, seguido por um cheque para RDTSC (para permitir um limite de tempo para que você não travar a máquina) está na faixa alta nannosecond (basicamente para baixo no ruído). Para o aperto de mão de nível de hardware que não deve demorar muito tempo a todos, é realmente difícil de bater um spinlock.

Meu 2c: Se suas atualizações satisfazer alguns critérios de acesso, então eles são bons candidatos spinlock:

  • rápido , ou seja, você terá tempo para adquirir o spinlock, executar as atualizações e solte o spinlock em uma única quanta fio para que você não se antecipou, mantendo o spinlock
  • localizada todos os dados que você atualização estão na preferência uma única página que já está carregado, você não quer um TLB perder enquanto você segurando o spinlock, e você definitivamente não quer um swap de falha de página ler!
  • atômica você não precisa de qualquer outro bloqueio para executar a operação, ou seja. Nunca esperam pelos bloqueios sob spinlock.

Para qualquer coisa que tenha qualquer potencial de rendimento, você deve usar uma estrutura de bloqueio notificado (eventos, mutex, semáforos etc).

Um caso de uso para bloqueios de rotação é se você esperar muito baixa contenção, mas vai ter um monte deles. Se você não precisa de suporte para bloqueio recursiva, um spinlock pode ser implementado em um único byte, e se disputa é muito baixo, então os resíduos ciclo de CPU é insignificante.

Para um caso de uso prático, eu muitas vezes têm matrizes de milhares de elementos, onde as atualizações para diferentes elementos do array podem acontecer de forma segura em paralelo. As probabilidades de dois tópicos que tentam atualizar o mesmo elemento, ao mesmo tempo são muito pequenas (de baixa contenção), mas eu preciso de um bloqueio para cada elemento (eu vou ter um monte deles). Nesses casos, eu normalmente alocar uma matriz de ubytes do mesmo tamanho que a matriz Estou atualizando em paralelo e implementar spinlocks em linha como (na linguagem de programação D):

while(!atomicCasUbyte(spinLocks[i], 0, 1)) {}
    myArray[i] = newVal;
atomicSetUbyte(spinLocks[i], 0);

Por outro lado, se eu tivesse de usar fechaduras normais, eu teria que alocar uma matriz de ponteiros para objetos, e, em seguida, alocar um objeto Mutex para cada elemento desta matriz. Em cenários como o descrito acima, este é um desperdício simplesmente.

Se você tem o código crítico desempenho e ter determinado que ele precisa ser mais rápido do que é atualmente e você determinou que o fator crítico é a velocidade de bloqueio, em seguida, que seria uma boa idéia para tentar um spinlock. Em outros casos, por que se preocupar? fechaduras normais são mais fáceis de usar corretamente.

Por favor, note os seguintes pontos:

  1. implementações rotação maioria de mutexe por algum tempo antes que o segmento é realmente marcação. Por isso, é difícil comparar teses mutexes com spinlocks puros.

  2. Vários tópicos spining "o mais rápido possível" no mesmo spinlock vai consome toda a largura de banda e drasticly diminuir a sua eficiência do programa. Você precisa adicionar minúsculo tempo "adormecido", acrescentando noop em seu loop spining.

Você raramente precisa usar spinlocks no código do aplicativo, se qualquer coisa que você deve evitá-los.

Eu não posso coisa de qualquer razão para usar um spinlock em c # código em execução em um sistema operacional normal. fechaduras ocupados são principalmente um desperdício no nível de aplicação - a fiação pode causar-lhe utilizar todo o timeslice cpu, contra um bloqueio será imediatamente causar uma mudança de contexto, se necessário.

High desempenho do código onde você tem nr de tópicos = nr de processadores / núcleos podem se beneficiar em alguns casos, mas se você precisa de otimização de desempenho nesse nível a sua tomada provável próximo jogo gen 3D, trabalhando em um sistema operacional incorporado com primitivas de sincronização pobres , criando um OS / driver ou em qualquer caso, usando c #.

Eu costumava bloqueios de rotação para a fase de stop-the-world do coletor de lixo na minha HLVM projeto porque eles são fáceis e que é um brinquedo VM. No entanto, bloqueios de rotação pode ser contra-produtivo nesse contexto:

Um dos erros perf em coletor de lixo de Glasgow Haskell Compiler é tão irritante que ele tem um nome, o " última núcleo desaceleração ". Esta é uma consequência directa do seu uso inadequado de spinlocks em sua GC e é excacerbated em Linux, devido ao seu programador, mas, na verdade, o efeito pode ser observado sempre que outros programas estão competindo por tempo de CPU.

O efeito é clara sobre o segundo gráfico aqui e pode ser visto afetando mais do que apenas o último núcleo aqui , onde o programa Haskell vê degradação do desempenho além de apenas 5 núcleos.

Sempre manter estes pontos em sua mente enquanto estiver usando spinlocks :

  • execução modo de usuário rápida.
  • Sincroniza tópicos dentro de um único processo, ou múltiplos processos se na memória compartilhada.
  • Não retorna até que o objeto pertence.
  • não suporta a recursividade.
  • consome 100% da CPU, enquanto "espera".

Eu pessoalmente tenho visto tantos impasses só porque alguém pensou que vai ser uma boa idéia usar spinlock.

Seja muito, muito cuidado ao usar spinlocks

(Eu não posso enfatizar isto o suficiente).

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