Mémoire nécessaire en 2024 pour exécuter 1 million de tâches concurrentes
(hez2010.github.io)- 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
tokioetasync_std, C# et NativeAOT, NodeJS, Pythonasyncio, 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
Ntâ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,
tokioetasync_std- Tous deux sont des runtimes asynchrones largement utilisés en Rust
- C# prend directement en charge
async/awaitet exécute les tâches avecTask.DelayetTask.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
setTimeoutavecutil.promisify, puis attend avecPromise.all - Python utilise
asyncio.sleepetasyncio.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 desawaitindividuels - 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
libicun’é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.