2 points par GN⁺ 2024-11-30 | Aucun commentaire pour le moment. | Partager sur WhatsApp
  • Benchmark comparant, avec les versions récentes des langages et runtimes fin 2024, l’usage mémoire de 1 à 1 million de tâches concurrentes ; il indique de consulter une page Take 2 séparée pour les résultats les plus récents
  • Tous les tests suivent la même structure : chaque tâche attend 10 secondes, puis le programme attend la fin de toutes les tâches ; l’objectif est de comparer les caractéristiques mémoire des coroutines, tâches asynchrones, goroutines et threads virtuels plutôt que celles de multiples threads
  • Les éléments comparés sont Rust tokio et async_std, C# et NativeAOT, NodeJS, Python asyncio, les goroutines Go, les virtual threads Java, ainsi que l’image native Java GraalVM ; tout le code est publié sur GitHub
  • À mesure que le nombre de tâches augmente, la progression de la mémoire varie fortement selon les runtimes ; à 1 million de tâches, C# affiche l’usage mémoire le plus faible, tandis que Rust conserve aussi des résultats efficaces
  • Le .NET récent montre de grands progrès et NativeAOT rivalise avec Rust, mais les goroutines Go utilisent, à 1 million de tâches, plus de 13 fois plus de mémoire que le résultat gagnant, et plus de 2 fois plus que Java

Méthode de benchmark et ressources publiées

  • Il s’agit d’une nouvelle exécution, avec les versions de langages les plus récentes fin 2024, de la comparaison 2023 de la consommation mémoire en programmation asynchrone
  • En haut de page, un message indique de consulter How Much Memory Do You Need in 2024 to Run 1 Million Concurrent Tasks? - Take 2 pour voir les résultats les plus récents
  • Le programme de test crée N tâches concurrentes reçues en argument de ligne de commande ; chaque tâche attend pendant 10 secondes, puis le programme se termine une fois toutes les tâches terminées
  • La comparaison porte non pas sur plusieurs threads, mais sur les modèles de concurrence de type coroutine
  • L’intégralité du code du benchmark est publiée dans async-runtimes-benchmarks-2024

Langages et runtimes comparés

  • Rust est comparé avec deux runtimes asynchrones, tokio et async_std
    • Tous deux sont des runtimes asynchrones largement utilisés en Rust
  • C# prend directement en charge async/await et exécute les tâches avec Task.Delay et Task.WhenAll
    • NativeAOT, disponible depuis .NET 7, est également comparé
    • NativeAOT compile directement le code managé en binaire final afin de pouvoir l’exécuter sans VM
  • NodeJS enveloppe setTimeout avec util.promisify, puis attend avec Promise.all
  • Python utilise asyncio.sleep et asyncio.gather
  • Go utilise les goroutines comme mécanisme de concurrence et attend la fin de toutes les tâches avec WaitGroup, plutôt qu’avec des await individuels
  • Java utilise les virtual threads disponibles depuis le JDK 21
    • L’image native de GraalVM est également comparée
    • L’image native GraalVM est incluse comme concept similaire à .NET NativeAOT

Environnement de test

  • Matériel : 13th Gen Intel Core i7-13700K
  • Système d’exploitation : 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
  • Lorsque c’était possible, tous les programmes ont été exécutés en release mode
  • Comme libicu n’était pas présent dans l’environnement de test, la prise en charge de l’internationalisation et de la globalisation a été désactivée

Évolution de la mémoire quand le nombre de tâches augmente

  • Empreinte minimale : 1 tâche

    • Pour observer la mémoire requise par le runtime lui-même, une seule tâche est d’abord exécutée
    • Rust, C# NativeAOT et Go sont compilés statiquement en binaires natifs, utilisent très peu de mémoire et affichent des résultats proches
    • L’image native Java GraalVM obtient aussi de bons résultats, mais consomme un peu plus de mémoire que les autres cibles compilées statiquement
    • Les programmes exécutés sur une plateforme managée ou un interpréteur consomment davantage de mémoire
    • Dans cette plage, Go affiche la plus petite empreinte
    • Java GraalVM utilise beaucoup plus de mémoire que Java OpenJDK, ce qui pourrait éventuellement être ajusté par configuration
  • 10 000 tâches

    • Les deux benchmarks Rust ne voient pas leur usage mémoire augmenter fortement par rapport à l’empreinte minimale, même avec 10 000 tâches, et restent très économes en mémoire
    • C# NativeAOT suit Rust de près, avec seulement environ 10 Mo de mémoire utilisée
    • L’usage mémoire de Go augmente fortement dans cette plage
    • Les virtual threads de l’image native Java GraalVM semblent plus légers que les goroutines Go
    • Go et l’image native Java GraalVM sont compilés statiquement en binaires natifs, mais utilisent plus de RAM que C# exécuté sur une VM
  • 100 000 tâches

    • Lorsque le nombre de tâches atteint 100 000, la consommation mémoire de tous les langages commence à augmenter fortement
    • Rust et C# obtiennent encore de bons résultats dans cette plage
    • C# NativeAOT utilise moins de RAM que Rust et devance tous les langages
    • À ce stade, le programme Go est à la traîne non seulement derrière Rust, mais aussi derrière Java, C# et NodeJS
    • Exceptionnellement, Java exécuté avec GraalVM est exclu des solutions qui battent Go
  • 1 million de tâches

    • À 1 million de tâches, C# devance nettement tous les autres langages
    • Rust continue, comme attendu, à obtenir de bons résultats en efficacité mémoire
    • L’écart entre Go et les autres runtimes se creuse encore
    • Go utilise plus de 13 fois plus de mémoire que le résultat gagnant
    • Même par rapport à Java, Go utilise plus de 2 fois plus de mémoire, un résultat contraire à l’idée répandue selon laquelle la JVM consomme beaucoup de mémoire tandis que Go serait léger

Observations finales

  • Avec un très grand nombre de tâches concurrentes, même si chaque tâche n’effectue pas de calcul complexe, elles peuvent utiliser une quantité importante de mémoire
  • Les compromis varient selon les runtimes de langage
    • Avec peu de tâches, ils peuvent être légers et efficaces
    • Lorsqu’ils passent à des centaines de milliers de tâches, la croissance de la mémoire peut devenir importante
  • Avec les compilateurs et runtimes récents, .NET montre de grands progrès
  • .NET NativeAOT produit des résultats compétitifs avec Rust
  • L’image native GraalVM de Java obtient aussi de bons résultats en efficacité mémoire
  • Les goroutines Go continuent de se montrer inefficaces en matière de consommation de ressources

Aucun commentaire pour le moment.

Aucun commentaire pour le moment.