Por que usar mais threads torna mais lento do que usar menos threads

30

Tentei executar o programa X usando 8 threads e acabou em n minutos .
Tentou executar o mesmo programa usando 50 threads e acabou em n * 10 minutos .

Por que isso acontece e como posso obter o número ideal de threads que posso usar?

PoGibas
fonte

Respostas:

33

Esta é uma pergunta complicada que você está fazendo. Sem saber mais sobre a natureza dos seus tópicos, é difícil dizer. Algumas coisas a considerar ao diagnosticar o desempenho do sistema:

O processo / thread

  • CPU vinculada (precisa de muitos recursos da CPU)
  • Memória ligada (precisa de muitos recursos de RAM)
  • Ligação de E / S (recursos de rede e / ou disco rígido)

Todos esses três recursos são finitos e qualquer um pode limitar o desempenho de um sistema. Você precisa observar quais (podem ser 2 ou 3 juntas) a sua situação específica está consumindo.

Você pode usar ntope iostat, e vmstatpara diagnosticar o que está acontecendo.

slm
fonte
8
O hardware também importa. Físico, virtual, número de núcleos, tipo de núcleo, cache L1 / L2 / L3, etc.
EightBitTony
46

"Por que isso acontece?" é fácil de responder. Imagine que você tem um corredor para acomodar quatro pessoas, lado a lado. Você deseja mover todo o lixo de um lado para o outro. O número mais eficiente de pessoas é 4.

Se você tiver de 1 a 3 pessoas, estará perdendo espaço no corredor. Se você tem 5 ou mais pessoas, pelo menos uma dessas pessoas fica presa na fila atrás de outra pessoa o tempo todo. Adicionar mais e mais pessoas apenas entope o corredor, não acelera a atividade.

Então, você deseja ter o maior número possível de pessoas, sem causar filas. Por que você tem filas (ou gargalos) depende das perguntas da resposta do slm.

EightBitTony
fonte
1
Seu exemplo é enganoso. Seria melhor dizer algo como: "Você tem um corredor que pode acomodar quatro pessoas, lado a lado, e é usado por você e outras pessoas para tarefas diferentes. Existe um árbitro que decide quem pode passar pelo corredor. Então o número mais eficiente de pessoas é maior que 4 e menor que algum número, onde suas pessoas começam a ficar em fila [altamente dependentes do contexto]. " Normalmente, ter alguns threads mais que o número de CPUs tem um desempenho melhor do que usar exatamente 4 threads. Se você é o único que usa a CPU, então 4é o melhor número.
Bakuriu 23/06
7
Ótimo exemplo, +1. Bakuriu, é um exemplo que ilustra o problema de um recurso compartilhado limitado. Está explicando o problema, não como encontrar o número ideal de threads.
Bananguin
1
Também seria útil ter em mente que os threads ainda têm seu próprio tipo de alternância de contexto que continua. Aumentar o número de threads não aumenta a capacidade de desempenho (como você apontou), mas também gasta o tempo da CPU, dando ao kernel mais trabalho a fazer. Basicamente, há retornos decrescentes no encadeamento e a execução de muitas causas retrógradas no desempenho.
Bratchley
9
Todo problema pode ser descrito em vários níveis de complexidade. Ofereci uma aproximação do problema, que acredito ser útil para explicar o básico. É claro que pode ser mais refinado e mais detalhado, mas quanto mais detalhado você o fizer, menos útil será como uma introdução ao problema.
precisa
Gostaria de acrescentar que, em vez de gastar muito tempo calculando o número ideal de threads, basta codificá-lo para que possa ser alterado facilmente. Qualquer mesclagem grande como essa exigirá inúmeras execuções de teste (a maioria com pequenos subconjuntos de dados) para aperfeiçoar. Aumente o número de encadeamentos até ver uma grande queda no desempenho ou no impacto em outras atividades do sistema que é inaceitável.
DocSalvager
20

Uma recomendação comum é n + 1 threads, sendo n o número de núcleos de CPU disponíveis. Dessa forma, n threads podem trabalhar na CPU enquanto 1 thread aguarda E / S de disco. Ter menos encadeamentos não utilizaria completamente o recurso da CPU (em algum momento sempre haverá E / S para aguardar), ter mais encadeamentos causaria brigas no encadeamento do recurso da CPU.

Os encadeamentos não são livres, mas com sobrecarga, como opções de contexto, e - se for necessário trocar dados entre os encadeamentos, o que geralmente é o caso - vários mecanismos de bloqueio. Isso vale apenas o custo quando você realmente possui mais núcleos de CPU dedicados para executar o código. Em uma CPU de núcleo único, um único processo (sem threads separados) geralmente é mais rápido do que qualquer encadeamento realizado. Os threads não magicamente tornam sua CPU mais rápida, apenas significa trabalho extra.

frostschutz
fonte
Essa deve ser a resposta geral, dada a quantidade de informações disponíveis em questão. nós não precisamos de uma tese completo soprado e filosofia como outras respostas
Allahjane
9

Como outros já apontaram ( resposta slm , resposta EightBitTony ), essa é uma pergunta complicada e, mais ainda, porque você não descreve o que você faz e como eles fazem.

Mas jogar definitivamente mais threads pode piorar as coisas.

No campo da computação paralela, existe a lei de Amdahl que pode ser aplicável (ou não, mas você não descreve os detalhes do seu problema, portanto ...) e pode fornecer algumas informações gerais sobre essa classe de problemas.

O ponto da lei de Amdahl é que em qualquer programa (em qualquer algoritmo) sempre existe uma porcentagem que não pode ser executada em paralelo (a parte sequencial ) e há outra porcentagem que pode ser executada em paralelo (a parte paralela ) [Obviamente essas duas partes somam 100%].

Essas partes podem ser expressas como uma porcentagem do tempo de execução. Por exemplo, pode haver 25% do tempo gasto em operações estritamente sequenciais, e os 75% restantes são gastos em operações que podem ser executadas em paralelo.

Imagem da Wikipedia (Imagem da Wikipedia )

A lei da Amdahl prevê que, para cada porção paralela (por exemplo, 75%) de um programa, você pode acelerar a execução apenas até o momento (por exemplo, no máximo 4 vezes), mesmo se você usar mais e mais processadores para fazer o trabalho.

Como regra geral, quanto mais você programar que não pode transformar em execução paralela, menos poderá obter usando mais unidades de execução (processadores).

Como você está usando threads (e não processadores físicos), a situação pode ser ainda pior do que isso. Lembre-se de que os threads podem ser processados ​​(dependendo da implementação e do hardware disponível, por exemplo, CPUs / Cores) compartilhando o mesmo processador / núcleo físico (é uma forma de multitarefa, como apontado em outra resposta).

Essa previsão teórica (sobre os tempos de CPU) não considera outros gargalos práticos como

  1. Velocidade de E / S limitada (velocidade do disco rígido e da rede)
  2. Limites de tamanho de memória
  3. Outras

isso pode ser facilmente o fator limitante em aplicações práticas.

DavAlPi
fonte
Essa resposta deve ser selecionada.
Eonil
6

O culpado aqui deve ser o "CONTEXTO SWITCHING". É o processo de salvar o estado do thread atual para começar a executar outro thread. Se um número de threads receber a mesma prioridade, eles precisarão ser alternados até concluir a execução.

No seu caso, quando existem 50 threads, ocorre muita alternância de contexto quando comparada com apenas 10 threads em execução.

Essa sobrecarga de tempo introduzida devido à alternância de contexto é o que torna seu programa lento

extremo
fonte
Como não sabemos quais são os tópicos, isso parece um palpite. Sim, a alternância de contexto adiciona uma sobrecarga, mas se os threads estiverem fazendo algum tipo de análise de dados, o problema poderá ser problemas de cache (ou seja, não poder usar o cache, porque toda vez que você alterna os threads, é necessário liberá-lo).
precisa
A alternância de contexto de thread por si só , a menos que lidemos com um grande número de alternâncias de contexto, provavelmente não terá um impacto de ordem de magnitude no desempenho. 50 threads são altos, mas não extremos (na minha caixa, no momento, são ps ax | wc -lrelatados 225 processos e, de maneira alguma, são pesados). Estou inclinado a seguir o palpite de @ EightBitTony; a invalidação do cache provavelmente é um problema maior, porque toda vez que você limpa o cache, a CPU precisa aguardar éons pelo código e dados da RAM.
a CVn
3

Para corrigir a metáfora do EightBitTony:

"Por que isso acontece?" é fácil de responder. Imagine que você tem duas piscinas, uma cheia e uma vazia. Você quer mover toda a água de uma para a outra e tem 4 baldes . O número mais eficiente de pessoas é 4.

Se você tiver de 1 a 3 pessoas, estará perdendo o uso de alguns baldes . Se você tiver 5 ou mais pessoas, pelo menos uma dessas pessoas ficará presa à espera de um balde . Adicionar mais e mais pessoas ... não acelera a atividade.

Então, você quer ter o maior número de pessoas possível para trabalhar (use um balde) simultaneamente .

Uma pessoa aqui é um encadeamento e um bucket representa qualquer recurso de execução que seja o gargalo. Adicionar mais threads não ajuda se eles não puderem fazer nada. Além disso, devemos enfatizar que a passagem de um balde de uma pessoa para outra é geralmente mais lenta do que uma única pessoa carregando apenas o balde na mesma distância. Ou seja, dois segmentos que se revezam em um núcleo normalmente executam menos trabalho do que um único segmento executando duas vezes mais: isso ocorre devido ao trabalho extra feito para alternar entre os dois segmentos.

Se o recurso de execução limitante (bucket) é uma CPU, um núcleo ou um pipeline de instruções hiperencadeado para seus objetivos, depende de qual parte da arquitetura é seu fator limitante. Observe também que estamos assumindo que os threads são totalmente independentes. Esse é apenas o caso se eles não compartilharem dados (e evitarem colisões de cache).

Como algumas pessoas sugeriram, para E / S, o recurso limitador pode ser o número de operações de E / S úteis na fila: isso pode depender de uma série de fatores de hardware e kernel, mas pode facilmente ser muito maior que o número de núcleos. Aqui, a alternância de contexto, que é tão cara em comparação com o código vinculado à execução, é bem barata em comparação ao código vinculado de E / S. Infelizmente, acho que a metáfora ficará completamente fora de controle se eu tentar justificar isso com baldes.

Observe que o comportamento ideal com o código vinculado de E / S ainda costuma ter no máximo um encadeamento por pipeline / núcleo / CPU. No entanto, você precisa escrever um código de E / S assíncrono ou síncrono / sem bloqueio, e a melhoria relativamente pequena do desempenho nem sempre justifica a complexidade extra.


PS. Meu problema com a metáfora original do corredor é que ele sugere fortemente que você deve ter quatro filas de pessoas, duas filas carregando lixo e duas voltando para coletar mais. Então você pode fazer cada fila de quase tão longo quanto o corredor, e adicionar pessoas fez acelerar o algoritmo (você basicamente virou todo o corredor em uma correia transportadora).

Na verdade, esse cenário é muito semelhante à descrição padrão da relação entre latência e tamanho da janela nas redes TCP, e é por isso que me ocorreu.

Sem utilidade
fonte
Não é uma metáfora, é uma aproximação projetada para explicar o sistema às pessoas de uma maneira que elas possam visualizá-lo facilmente. Como tal, sempre será 'descartado' por pessoas que conhecem o próximo nível de detalhe, mas não percebem que seu nível de detalhe não é realmente necessário para iniciantes. Ninguém aprende física de partículas começando no nível de doutorado. Todo o material anterior é uma aproximação que o leva gradualmente a refinar à medida que avança. Não é "errado", não é apenas a imagem completa.
precisa
Ninguém está confuso sobre qual figura de linguagem você usou, e isso não é uma analogia ruim. Toda analogia tem algum limite além do qual diverge da coisa que deveria descrever e deixa de ser útil. Eu apenas mencionei isso porque o original me lembrou muito fortemente de um cenário diferente e porque eu não acho essa versão mais complexa para a (esperançosamente) melhor previsão.
Inútil
0

É bem direto e simples de entender. Tendo mais threads do que o que sua CPU suporta, você está realmente serializando e não paralelizando. Quanto mais threads você tiver, mais lento será o seu sistema. Seus resultados são realmente uma prova desse fenômeno.

Bruno Taboada
fonte