Quanta memória você precisa em 2024 para executar 1 milhão de tarefas concorrentes?
(hez2010.github.io)Foi executado um programa que processa 1 milhão de tarefas concorrentes nas versões mais recentes de Rust, C#, Python, Go, Java e NodeJS para comparar a eficiência de memória.
C# (NativeAOT) e Rust mostraram a melhor eficiência de memória, enquanto Go recebeu uma avaliação inferior devido ao consumo de memória acima do esperado. No geral, o desempenho de .NET e Rust se destacou, e Java (GraalVM) apresentou uma melhora surpreendente.
Especificamente, Rust usou a menor quantidade de memória, com cerca de 29MB, seguido por C# NativeAOT com cerca de 71MB. NodeJS registrou 232MB, e Python, 339MB. Go apresentou um resultado decepcionante, com uso de memória relativamente alto, de 753MB. Java (GraalVM) mostrou uma grande melhora, com 92MB.
10 comentários
Pelo código de benchmark, no caso de Rust e Python, parece que na prática não estão criando tarefas concorrentes de fato, mas apenas objetos
futureque, embora sejam assíncronos, não conseguem rodar em paralelo com outras tarefas. Imagino que no C# talvez seja um caso parecido. Já o código em Go criagoroutine, que é uma tarefa com sua própria call stack e afins; talvez seja por isso que, no caso de 1 milhão, o uso de memória do Go pareça se destacar tanto.Para defender o Go: o Go funciona mesmo que haja qualquer biblioteca dentro de 1 milhão de funções em execução. Basta colocar
go. Em outras linguagens baseadas em assíncrono, se houver no meio uma biblioteca síncrona que consuma um pouco de tempo, acaba acontecendo a situação insana em que isso elimina todas as vantagens do assíncrono.Para aproveitar 100% das vantagens do assíncrono, todas as funções que consumam nem que seja um pouco de tempo precisam ser convertidas para assíncronas.
Java VirtualThread, hum... desta vez, na nossa empresa, fomos confiando em Java VirtualThread, mas por causa de compatibilidade com bibliotecas no fim tivemos que voltar para threads normais, e o desfecho foi subir dezenas de instâncias.
Você poderia falar um pouco mais sobre a parte de compatibilidade? :eyes:
Acho que dá para dizer que já não faz sentido aquela frase comum no Spring de que "para usar WebFlux direito, em vez de JPA é preciso usar junto com R2DBC para ver seu verdadeiro valor".
Ouvi dizer que a biblioteca
msalda MS não funciona com virtual threadsA biblioteca
msalcitada como exemplo me parece um caso semelhante também em Go, se for uma biblioteca que usa tipos de dados ou estruturas que não são thread-safe.Qual é a relação disso com thread safety?
goroutinenão é, desde o início, um sistema que garanta thread safety.Obrigado pelas informações
Tenho uma pergunta.
Então, nas outras linguagens além de Go, se houver uma biblioteca síncrona, tudo acaba quebrando como você mencionou?
Ou será que entre outras linguagens também existe alguma que, como Go, suporte assincronia de forma completa?
Existe a expressão "colorless". Go é a única linguagem em que não há necessidade de distinguir entre assíncrono e síncrono. Do ponto de vista do usuário, quando se faz programação que exige concorrência, Go tem uma vantagem esmagadora em termos de dificuldade e usabilidade.
Talvez o desempenho fique um pouco abaixo de uma programação assíncrona otimizada, claro.
Desculpe a observação, mas vou apontar apenas as partes incorretas das expressões "quebra" e "assíncrono perfeito". Mesmo em código assíncrono, não há problema em usar bibliotecas síncronas, desde que haja garantia de que elas terminem em pouco tempo. O problema surge quando há chamadas de longa duração executadas de forma síncrona, porque isso atrasa o processamento de outras tarefas assíncronas. Em Go, como é possível atribuir centenas de milhões de tarefas com goroutines, não existe o conceito de assíncrono na própria linguagem. Para quem usa, é extremamente conveniente, porque qualquer função pode ser chamada em paralelo apenas adicionando
go. Pessoalmente, acho que o "assíncrono perfeito" talvez seja o JavaScript, cuja própria base da linguagem é assíncrona.