2 pontos por GN⁺ 2024-11-30 | Ainda não há comentários. | Compartilhar no WhatsApp
  • 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 tokio e async_std, C# com NativeAOT, NodeJS, Python asyncio, 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 N tarefas 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: tokio e async_std
    • Ambos são runtimes assíncronos amplamente usados em Rust
  • C# oferece suporte direto a async/await e executa as tarefas com Task.Delay e Task.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 setTimeout com util.promisify e depois aguarda com Promise.all
  • Python usa asyncio.sleep e asyncio.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.

Ainda não há comentários.