Reactivecocoa: Atômico: SPINLOCK não é seguro no iOS

Criado em 15 dez. 2015  ·  20Comentários  ·  Fonte: ReactiveCocoa/ReactiveCocoa

https://twitter.com/steipete/status/676851647042203648

Vou abster-me de fazer comentários (vou apenas dizer ... se é ilegal no iOS, por que é uma API pública ???)

Aparentemente, os mutexes pthread são mais rápidos agora . Devemos criar uma referência para comparar e tentar converter Atomic para isso (também RACCompoundDisposable e RACSerialDisposable ?)

help wanted

Comentários muito úteis

Eu descobri esse tópico hoje, fiz uma investigação sobre diferentes tipos de sincronização e executei um benchmark no simulador iOS e dispositivos reais.
Os resultados são muito interessantes para mim. No iOS 10, temos degradação de desempenho visível de dispatch_semaphore, que provavelmente mudou seu comportamento com respeito à prioridade de thread.
Aqui está o diagrama de resumo dos mecanismos básicos de sincronização disponíveis no iOS. Todos os testes são executados com configuração de versão (otimização Swift -O)

2016-06-29 17 58 39

Código de referência para SDK9
Código de referência para SDK10

Todos 20 comentários

Eu nem tenho certeza do que "ilegal" significa.

nope

Estou apenas curioso, já que não estava por perto desde o início, mas por que OSSpinLock usado para o tipo Atomic para começar?

NSLock usado e mostrou ser muito lento, então pthread_mutex_t foi testado e considerado muito lento também? Infelizmente git não tem histórico antes de "mover fontes ..."

Spinlocks não são algo que eu pensaria em usar em um contexto de driver de dispositivo não kernel, e é por isso que eu pergunto. Se alguma coisa, eu diria que o bug aqui é que OSSpinLock é usado. : stick_out_tongue:

Para adicionar um contraponto ao meu comentário acima, isso não é tão grande quanto o título pode parecer :

Mesmo com a falta de CPU, essa seção crítica é tão pequena que provavelmente está ok em situações reais.
- @Catfish_Man no Twitter

Quanto ao comentário acima de @kastiglione :

Eu nem tenho certeza do que "ilegal" significa.

Não tenho a resposta, mas acho que algo na CPU ARM não permite que a primitiva spinlock aproveite muitos dos mesmos benefícios / garantias / suposições que faz nas CPUs Intel. (Mas provavelmente não todos eles, de acordo com https://twitter.com/Catfish_Man/status/676851988596809728)

De qualquer forma, provavelmente não há "nada para ver aqui" na prática e, em vez disso, alguma investigação deve ser feita para substituí-la pela primitiva mais segura e medir qualquer regressão de desempenho, como @NachoSoto sugere.

Acho que devemos substituir nossos usos de OSSpinLock , mas acho que nosso / meu raciocínio para usá-los foi válido dada a documentação disponível para nós.

Sim, e obrigado pelos commits de história relevantes. Não sei por que o GitHub não faz um git log --follow quando o histórico é solicitado para um determinado arquivo.

De qualquer forma, vou reiterar que "provavelmente está tudo bem". Usar um mutex "agora mais rápido" é provavelmente uma tarefa simples, mas nem tanto sem saber como você criou o perfil / testou para começar. Tem alguma sugestão / sugestão aí?

Quanto a um caminho a seguir se / quando o desempenho se tornar uma preocupação, vale a pena investigar as filas sem bloqueio. (Aqui está um artigo muito bom para leitores não familiarizados com o conceito: http://www.linuxjournal.com/content/lock-free-multi-producer-multi-consumer-queue-ring-buffer?page=0,0). Eu construí algo semelhante para uma biblioteca de áudio interna há algum tempo e posso adaptá-lo para uso geral como este.

Usar um mutex "agora mais rápido" é provavelmente uma tarefa simples, mas nem tanto sem saber como você criou o perfil / testou para começar. Tem alguma sugestão / sugestão aí?

Eu estava executando o GitHub Desktop no Time Profiler and Allocations da Instruments e me concentrando em pilhas pesadas em RAC. Você terá que falar com @joshaber ou @mdiep para isso agora. :piscadela:

OK bom saber. Eu imagino que as coisas do lado do Swift exigirão uma base de código igualmente grande / profunda para testar. Eu mesmo desenvolvi um pouco nos últimos meses 1, mas não sei se empurro o RAC com "força suficiente" para obter amostras suficientes dele. Muitas outras coisas tendem a aparecer nos gráficos de instrumentos com aplicativos como o meu. :sorriso:

1 Plug! http://capoapp.com , com http://capoapp.com/neptune sendo o componente mais recente, totalmente projetado usando RAC4.

Escrevi uma postagem no blog hoje que dá mais detalhes sobre por que spinlocks são "ilegais" no iOS: http://engineering.postmates.com/Spinlocks-Considered-Harmful-On-iOS/

Eu interrompi o Atomic há pouco tempo e o migrei para pthread_mutex_lock. Para todos os efeitos, é o melhor bloqueio que existe - sem despacho dinâmico como o NSLock, o bloqueio instantâneo, se não contido como o spinlock, não desperdiça energia ao contrário do bloqueio de rotação.

Sinta-se à vontade para copiar e colar o código-fonte do meu repositório ou fazer o que quiser com ele.

https://github.com/Adlai-Holler/Atomic
https://github.com/Adlai-Holler/Atomic/blob/master/Atomic/Atomic.swift

@kballard essa é uma ótima explicação, obrigado!

Não tenho opiniões fortes sobre isso, pois não sou um especialista no assunto. Vou passar para os outros a decisão de alterar (ou não) o tipo de bloqueio que usamos, embora eu mesmo fique feliz em fazer a alteração!

Um benchmark como esse é completamente inútil sem a fonte. Além do mais, você está mostrando apenas um único tempo para cada construção, sem documentar o que esse tempo realmente representa. As várias construções irão se comportar de forma diferente dependendo se estão sob contenção, as prioridades dos threads que estão competindo na fechadura, etc. @synchronized também é uma besta curiosa, seu desempenho dependerá muito dos padrões de acesso , como quantos blocos @synchronized estão sendo inseridos / encerrados ao mesmo tempo, se você está bloqueando o mesmo objeto repetidamente ou bloqueando novos objetos, etc.

NB: dispatch_semaphore não doa prioridade , o que é outra armadilha potencial.

@jspahrsummers @kballard Eu tenho uma implementação usando CAS atômico. Se houver algum interesse, terei prazer em enviar um PR.

editar: Observando o uso de Atomic , não acho que minha implementação será útil. Se você pode fazer atualizações idempotentes, o CAS permite fazer um tipo de transações otimistas na memória que pode ser ótimo, mas que exigiria um pouco de refatoração (embora funcione muito bem se tudo for um CoW imutável, então você realmente pode tratar a mutação como uma transação na memória).

Eu descobri esse tópico hoje, fiz uma investigação sobre diferentes tipos de sincronização e executei um benchmark no simulador iOS e dispositivos reais.
Os resultados são muito interessantes para mim. No iOS 10, temos degradação de desempenho visível de dispatch_semaphore, que provavelmente mudou seu comportamento com respeito à prioridade de thread.
Aqui está o diagrama de resumo dos mecanismos básicos de sincronização disponíveis no iOS. Todos os testes são executados com configuração de versão (otimização Swift -O)

2016-06-29 17 58 39

Código de referência para SDK9
Código de referência para SDK10

Seus números parecem muito suspeitos para mim. Quantas vezes você fez os testes?

Para efeito de comparação, executei meu próprio benchmark ( fonte ) no OS X 10.11.5 usando um chicote de benchmark hacky que escrevi um tempo atrás. Executei todo o benchmark 10 vezes, retirei os 2 primeiros e os 2 primeiros números de cada teste (para me livrar dos outliers) e o restante dos números produziram os seguintes intervalos:

sem sincronização: 22-41 ns
spinlock: 24ns
semáforo: 29ns
NSLock: 45-71ns
mutex: 40-64ns
sincronizado: 75-122ns
fila: 505-554ns

Disto, você pode ver que spinlock é de longe o mais barato, seguido por semáforo, então mutex, então NSLock apenas ligeiramente atrás de mutex (já que é basicamente mutex + objc_msgSend), então sincronizado é quase duas vezes mais caro e, finalmente, as filas acabam sendo extremamente caro, muito mais do que eu esperava.

@kballard como você pode ver, meu benchmark é bastante simples, mas usa o mecanismo xctest padrão para medir o tempo de execução. Os números representam o tempo médio de execução de cada teste, não de cada sincronização. Em geral, seus e meus resultados estão correlacionando filas de exceção, que não são tão ruins, como costumávamos pensar

razões

Esta página foi útil?
0 / 5 - 0 avaliações