Respostas:
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
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 ntop
e iostat
, e vmstat
para diagnosticar o que está acontecendo.
"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.
4
é o melhor número.
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.
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 )
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
isso pode ser facilmente o fator limitante em aplicações práticas.
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
ps ax | wc -l
relatados 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.
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.
É 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.