Executando verificação de segurança...
-1

Usar mais threads é mais lento?

Muita gente acredita que usar mais threads em um software em 100% dos casos o deixará mais rápido, o que não está certo. Mas do outro lado da moeda existem pessoas que acreditam que usar mais threads é "mais lento" dando a explicação de que você basicamente está dividindo a mesma capacidade de CPU para duas threads.

As duas percepções estão erradas e o primeiro erro é o causador do segundo. Acontece que o uso de múltiplas threads é benéfico para a performance de um software quando você as usa para uma tarefa onde a ordem de execução não importa.

Um exemplo disso é quando você precisa fazer o mesmo processamento de dados em dois ou mais arquivos ao mesmo tempo. Como cada arquivo tem um processamento individual e não importa a ordem em que você os processa, você pode se beneficiar do uso de múltiplas threads para processar múltiplos arquivos ao mesmo tempo.

Mas um desenvolvedor que não entenda isso e acredita que o uso de threads sempre deixa tudo mais rápido, pode acabar tentando usar threads para executar uma tarefa em que a ordem de execução importa. Que seria o caso de tarefas sequenciais, onde o próximo passo depende da finalização do passo anterior.

O problema de tentar usar threads para resolver tarefas sequenciais é que você vai precisar fazer a sincronização das threads, fazendo com que com frequência as threads fiquem em estado ocioso. Ou seja, desse jeito fazendo com o que o software perca performance ao invés de ganhar.

Como ocorrem vários casos de desenvolvedores tentarem "otimizar" software forçando o uso de threads onde não faziam sentido e, por consequência, prejudicando a performance, surgiu então outra percepção errada do motivo desta perda de performance.

Algumas pessoas acreditam que a perda de performance ocorre porque a CPU tem uma capacidade N que é dividida igualmente com todas as threads. Então se você tem uma thread, você tem N/1 para a sua thread e se você tem duas threads, você tem N/2 para cada uma das 2 threads. Ou seja: N/2 * 2 = N... Daí a percepção de que "não muda nada" e que isso apenas torna o software "mais lento" já que agora você adiciona a complexidade de ficar trocando a execução entre as threads.

Mas essa percepção está tecnicamente errada porque ela não condiz com como softwares funcionam na vida real. Isso estaria correto apenas no cenário imaginário de uma CPU single-core rodando um único processo, mas na vida real as coisas são muito mais complexas do que isso.

Na vida real seu software roda sobre um sistema operacional e compartilha recursos com milhares de threads de outros processo em execução. Digamos que hajam 100 threads ao todo no sistema e 1 para o seu software, o que significa que o seu software usa 1/100 da capacidade do processador. Adicione mais uma thread e agora seu sofware usa 2/101 da capacidade do processador.

Não é o dobro de performance como a galera que não entende de concorrência acha que é, mas existe de fato um ganho de performance (2/101 > 1/100). Não é a mesma capacidade sendo dividida por dois igual algumas pessoas acreditam. Essa percepção também não leva em consideração multicore, hyper-threading e a priorização de tarefas do kernel.

Sobre este último: Quanto mais CPU uma thread consome, menos ela é priorizada pelo kernel. Logo dividir o trabalho em dois (ou mais) pode evitar que o processo tenha a performance prejudicada pelo escalonador de tarefas.


Se quiser aprender mais sobre performance, eu escrevi um artigo sobre isso aqui: https://www.tabnews.com.br/Silva97/mitos-da-otimizacao-de-codigo

Carregando publicação patrocinada...
1

Eu já escrevi sobre isso: https://www.tabnews.com.br/maniero/e-sempre-garantido-que-uma-aplicacao-com-multiplas-threads-rode-mais-rapido-que-usando-uma-unica-thread.

Uma coisa que muita gente acredita é que é necessário de threads para resolver questão de IO e não é, na verdade nem é o correto, apesar de funcionar. Para IO a solução correta é assincronismo, inclusive o sistema operacional tem mecanismos próprios para lidar com isso e está disponível em bibliotecas diversas. Ao mesmo tempo que acham que código assíncrono sempre deixa mais rápido, mas não acontece por si só em código dependente de CPU, ele é bom para IO.

No passado a única solução que existia para IO era threads mesmo, hoje não é assim, mas muita gente aprendeu assim, pega materiais antigos e acha que é necessário ainda.

O uso correto de threads é quando tem muito processamento que pode rodar concorrentemente sem maiores problemas, ou seja, só vale a pena quando oa CPU ficaria ociosa de outra jeito.

Mas para CPU bound geralmente só vale a pena usar a quantidade de threads igual ao número de processadores disponíveis (eventualmente até virtual pode dar algum ganho, mas só em alguns cenários). Porque é desta forma que você pode aproveitar todos os processadores. Se você usar mais threads que o número de processadores começa ficar mais lento porque você paga o custo de troca de contexto de threads que não é barato, por isso algumas linguagens prefeem mecanismo que não dependem de treads diretamente para processamento concorrente, e começa dividir o a capacidade de cada processador, então é só perda.

Já com IO bound isso não acontece porque o proc essador ficaria ocioso nos momentos que está fazendo IO e o uso da thread permite que o processador seja usado e assim ganha tempo, mesmo que não precise usar isso hoje em dia para ter esse ganho, mas é uma forma possível.

O problema do travamento não acontece só em tarefas sequenciais e em algumas sequencias é possível não ter travamento, não é tão simples definir isso, cada algoritmo pode exigir algo diferente.

Então a forma correta de pensar em CPU bound é ter mais threads que processadores, não tem a ver com o fato de ter 1 ou mais processadores, você pode 64 processadores, você não terá 64 vezes mais velocidade tendo 64 threads por causa do custo de administração de threads. Mas fica pior de você tiver mais threads, por exemplo se tiver 128 ficará mais lento que se tiver 64, porque além de não ter ganho porque só tem 64 processadores e eles começam ter sua capacidade dividida, mas também porque a administração fica mais cara por mais trocas de contexto, que inclusive pode afetar o uso de memória, já que pode afetar o cache, e o custo tem potencial de quase inviabilizar o uso em alguns casos. Novamente, não estamos falando de IO bound aqui.

Se o problema for que seu sistema está usando poucas threads e outros sistemas usam muitas, você aumentar a quantidade de threads do seu sistema ajuda o seu sistema, mas piora outras coisas, ou seja, pode não ser uma boa solução. Na verdade esse padrão mostra que algo foi mal planejado, se você precisa de performance no seu sistema ele não deveria concorrer com outros processos, algum dimensionamento está errado, precisa de uma máquina que não atende a demanda, e não é solução uma sistema ferrar os demais. Em uma máquina adequada as outras threads estão dormindo ou em uso muito pequeno.

E quando precisa de mais prioridade o sistema operacional tem maneiras de entregar isso sem ter que criar thrads extras artificialmente para "roubar" processamento dos outros processos, e é mais "educado" fazer desta forma.

Eu gostaria de ver alguma fonte que diga que o kernel dá menos prioridade para quem usa mais.

S2


Farei algo que muitos pedem para aprender a programar corretamente, gratuitamente (não vendo nada, é retribuição na minha aposentadoria) (links aqui no perfil também).

0

Seu post tem boas intenções e acerta em cheio ao dizer que "as coisas são mais complicadas na vida real", mas a maior parte das explicações está simplesmente equivocada ou simplificada demais.

Por exemplo a ideia que de dividir um problema CPU bound em mais thread o deixaria mais rápido por que o processo teria mais vruntime, um benchmark simples mostrou que não para de pé.

-1

Obrigado por tentar contribuir com a qualidade do conteúdo, mas a correção apresentada não está corrigindo algo que realmente está escrito no texto do post.

O que realmente está escrito é uma demonstração bem simples (porém suficiente para compreensão da informação) do porque a percepção de que adicionar mais uma threads divide a capacidade da CPU em partes iguais para as duas threads não está correta. O que eu demonstro mencionando o fato de que na vida real a CPU não está 100% dedicada há um único software mas sim a centenas ou até milhares deles.

E para demonstrar como realmente mais uma thread não "divide pela metade" (N/2) eu fiz aquela explicação de que não é N/1 -> N/2 mas sim 1/N -> 2/(N+1) quando você muda de uma thread para duas.

De forma alguma está escrito no texto que isso necessariamente se converte em um software mais rápido. Está claro no texto que isso não é verdade em todo caso e, inclusive, explica em qual caso usar threads prejudica a performance.

Também está claro no texto que existem outros fatores que precisam ser levados em consideração, como o fato do kernel diminuir a prioridade de threads que estejam usando muita CPU.

-1

Quanto mais CPU uma thread consome, menos ela é priorizada pelo kernel. Logo dividir o trabalho em dois (ou mais) pode evitar que o processo tenha a performance prejudicada pelo escalonador de tarefas.

Dividir em mais threads um problema cpu bound não melhora a perfomance. Eu testei. E sugiro que teste também, vc escreve implicitamente que pode melhorar a perfomance sim, em qual caso?
Tem algum exemplo concreto que mostra isso?

-2

«Dividir em mais threads um problema cpu bound[...]»

Esse que é o problema, eu não disse isso em lugar nenhum. Nem disse que melhora nem que piora a performance. Quando eu falo de "dividir o trabalho" eu me refiro a execução do software no geral, não de um algoritmo.

Troca de contexto nem sempre é ruim, é importante dar espaço para outros processos senão o escalonador vai prejudicar a performance do seu software. É por isso que softwares que consomem CPU em excesso começam a travar — é o kernel fazendo isso.

Além disso, você também não quer prejudicar a execução de outros processos a menos que o software execute em um contexto em que isso faça sentido.

Sobre exemplos: O simples fato do GNU make ter a opção -j já serve muito bem para exemplificar o que eu estou dizendo. E também provar na prática que o uso de threads assim realmente acarreta em ganho de performance. Faça o teste: Compile um projeto com e sem -j e veja em qual dos dois casos termina mais rápido.

Você pode ver outros softwares com opções semelhantes, como o -threads do ffmpeg.

E antes que diga o que eu estou prevendo que dirá, repito: «Dividir em mais threads um problema cpu bound[...]» não foi o que eu disse. Então não responda dizendo que nem o make nem o ffmpeg fazem isso. Realmente não fazem.

-5

Mas do outro lado da moeda existem pessoas que acreditam que usar mais threads é "mais lento" dando a explicação de que você basicamente está dividindo a mesma capacidade de CPU para duas threads.

Começou o texto afirmando isso e terminou com aquilo. Mas claro deve ser só eu que não sei interpreter texto que viajei achando que estava falando de um problema cpu bound quando apenas estava se referindo a capacidade de processamento da cpu como a execução do software no geral e não de trabalho real da cpu, my bad.

Claro se a cpu ta ociosa usar mais threads é um jeito muito eficaz de ocupar ela. Mas não foi isso que entendi do seu texto.

Enfim um abraço e bons estudos muchacho.

-1

Meu caro, leia de novo o que eu acabei de falar. Dei dois exemplos que você pode testar na prática para ver se usar mais threads beneficia ou não: make e ffmpeg.

Compilação de código e processamento de vídeo são exemplos de problemas CPU bound ou de tempo ocioso? Você sabe que são exemplos de problemas de CPU e não de ociosidade. E você também sabe que os dois exemplos mencionados ganham performance ao usar múltiplas threads. É por isso que eles têm opção para isso. Se prejudicasse a performance não teria motivos para os desenvolvedores deixarem essas opções aí.

Agora se você tem interesse em entender a diferença entre o que o make e o ffmpeg faz (que é sobre o que eu estou falando neste post) contra o que você estava falando sobre dividir o problema em mais threads — que realmente não implica em melhoria de performance, eu deixo como dever de casa. É bem simples de entender, na verdade.

Sobre a afirmação inicial, também está claro no texto sobre o que eu estava falando e de onde surgiu esta percepção errada. Está tudo muito bem explicado, basta ler.