Quanta memória é necessária em 2024 para executar 1 milhão de tarefas simultâneas
(hez2010.github.io)- Trata-se de um benchmark que compara o uso de memória de 1 até 1 milhão de tarefas simultâneas com base nas linguagens e runtimes mais recentes no fim de 2024, com um aviso para consultar os resultados mais novos em uma página separada de Take 2
- Todos os testes seguiram a mesma estrutura: cada tarefa espera 10 segundos e depois o programa aguarda a conclusão total, comparando as características de memória de corrotinas, tarefas assíncronas, goroutines e virtual threads, em vez de várias threads
- Os alvos de comparação incluem Rust
tokioeasync_std, C# com NativeAOT, NodeJS, Pythonasyncio, goroutines de Go, virtual thread de Java e native image do GraalVM para Java, com todo o código publicado no GitHub - À medida que o número de tarefas aumentou, a variação no crescimento de memória entre os runtimes foi grande, e com 1 milhão de tarefas o C# apresentou o menor uso de memória, enquanto Rust também manteve resultados eficientes
- O .NET mais recente mostrou grande melhora e o NativeAOT competiu com Rust, mas as goroutines de Go usaram mais de 13 vezes a memória do vencedor e mais de 2 vezes a de Java no cenário com 1 milhão de tarefas
Método do benchmark e materiais públicos
- Este é o resultado de refazer, com as versões mais recentes das linguagens no fim de 2024, a comparação de consumo de memória de programação assíncrona feita em 2023
- No topo há um aviso para consultar How Much Memory Do You Need in 2024 to Run 1 Million Concurrent Tasks? - Take 2 para ver os resultados mais atuais
- O programa de teste cria
Ntarefas simultâneas recebidas por argumento de linha de comando, cada uma espera por 10 segundos, e o processo termina quando todas as tarefas acabam - O foco da comparação não está em várias threads, mas em modelos de concorrência da família de corrotinas
- Todo o código do benchmark está disponível em async-runtimes-benchmarks-2024
Linguagens e runtimes comparados
- Rust é comparado com dois runtimes assíncronos:
tokioeasync_std- Ambos são runtimes assíncronos amplamente usados em Rust
- C# oferece suporte direto a
async/awaite executa as tarefas comTask.DelayeTask.WhenAll- O NativeAOT fornecido desde o .NET 7 também entra na comparação
- NativeAOT compila diretamente para um binário final, permitindo executar código gerenciado sem VM
- NodeJS encapsula
setTimeoutcomutil.promisifye depois aguarda comPromise.all - Python usa
asyncio.sleepeasyncio.gather - Go usa goroutine como componente de concorrência e, em vez de await individual, espera a conclusão de todas as tarefas com
WaitGroup - Java usa virtual thread, disponível a partir do JDK 21
- O native image do GraalVM também é comparado
- O GraalVM native image entra como um conceito semelhante ao .NET NativeAOT
Ambiente de teste
- Hardware: 13th Gen Intel Core i7-13700K
- Sistema operacional: Debian GNU/Linux 12(bookworm)
- Rust: 1.82.0
- .NET: 9.0.100
- Go: 1.23.3
- Java: openjdk 23.0.1 build 23.0.1+11-39
- Java(GraalVM): java 23.0.1 build 23.0.1+11-jvmci-b01
- NodeJS: v23.2.0
- Python: 3.13.0
- Sempre que possível, todos os programas foram executados em release mode
- Como o ambiente de teste não tinha
libicu, o suporte a internacionalização e globalização foi desativado
Variação de memória com o aumento do número de tarefas
-
Pegada mínima: 1 tarefa
- Primeiro foi executada apenas 1 tarefa para observar a memória exigida pelo próprio runtime
- Rust, C# NativeAOT e Go foram compilados estaticamente em binários nativos, usaram pouquíssima memória e mostraram resultados parecidos entre si
- O Java GraalVM native image também teve bom resultado, mas usou um pouco mais de memória do que os outros alvos com compilação estática
- Programas executados sobre plataformas gerenciadas ou interpretadores consumiram mais memória
- Nessa faixa, Go apresentou a menor pegada
- O Java GraalVM usou muito mais memória que o Java OpenJDK, embora isso possa talvez ser ajustado por configuração
-
10 mil tarefas
- Nos dois benchmarks de Rust, o uso de memória em 10 mil tarefas não cresceu muito em relação à pegada mínima, mantendo-se muito baixo
- O C# NativeAOT também usou apenas cerca de 10MB de memória e ficou logo atrás de Rust
- O uso de memória de Go aumentou bastante nessa faixa
- A virtual thread do Java GraalVM native image pareceu mais leve do que a goroutine de Go
- Go e Java GraalVM native image foram compilados estaticamente em binários nativos, mas ainda assim usaram mais RAM do que C#, que roda sobre VM
-
100 mil tarefas
- Quando o número de tarefas subiu para 100 mil, o consumo de memória de todas as linguagens começou a crescer bastante
- Rust e C# continuaram mostrando bons resultados nessa faixa
- O C# NativeAOT usou menos RAM que Rust e liderou entre todas as linguagens
- Nesse ponto, o programa em Go ficou atrás não só de Rust, mas também de Java, C# e NodeJS
- Como exceção, o Java executado no GraalVM foi excluído do grupo que superou Go
-
1 milhão de tarefas
- Com 1 milhão de tarefas, o C# superou claramente todas as outras linguagens
- Rust continuou, como esperado, com bons resultados em eficiência de memória
- A diferença entre Go e os outros runtimes aumentou ainda mais
- Go usou mais de 13 vezes a memória do resultado vencedor
- Mesmo em comparação com Java, Go usou mais de 2 vezes a memória, mostrando um resultado diferente da percepção comum de que a JVM usa muita memória e Go é leve
Observações finais
- Quando o número de tarefas simultâneas é muito alto, elas podem consumir uma quantidade considerável de memória mesmo sem fazer cálculos complexos
- Os trade-offs aparecem de forma diferente em cada runtime de linguagem
- Com poucas tarefas, ele pode ser leve e eficiente
- Ao escalar para centenas de milhares de tarefas, o aumento de memória pode se tornar grande
- Com base nos compiladores e runtimes mais recentes, o .NET mostrou grande melhoria
- O .NET NativeAOT apresentou resultados competitivos com Rust
- O GraalVM native image de Java também teve bons resultados em eficiência de memória
- As goroutines de Go continuaram mostrando resultados ineficientes em termos de consumo de recursos
Ainda não há comentários.