Le problème de Mono dans Unity : pourquoi votre code C# s’exécute plus lentement que prévu
(marekfiser.com)- Le runtime Mono utilisé par Unity affiche des vitesses d’exécution nettement inférieures à celles du .NET moderne, avec des cas où le même code C# présente un écart pouvant aller jusqu’à 15x
- Dans du vrai code de jeu, l’exécution Unity basée sur Mono prend 100 secondes, contre 38 secondes pour le même code sous .NET, ce qui affecte fortement l’efficacité du débogage et des tests
- Même en mode Release, Mono met 30 secondes contre 12 pour .NET, ce qui maintient un écart de performance supérieur à 2,5x même dans un environnement optimisé
- La cause vient du JIT inefficace de Mono, de ses échecs d’inlining et de copies mémoire excessives, en contraste avec les optimisations modernes du JIT CoreCLR de .NET
- Si Unity achève sa modernisation .NET basée sur CoreCLR, des gains de performance majeurs seront possibles à la fois dans les jeux et dans l’éditeur, ce qui pourrait supprimer cette taxe de performance cachée pour tous les projets Unity
Pourquoi Unity utilise Mono
- Unity utilise le framework Mono pour exécuter le code C# depuis 2006
- À l’époque, Mono était la seule implémentation .NET multiplateforme, et son caractère open source permettait à Unity de le modifier
- À partir de 2014, Microsoft a publié .NET Core en open source, puis lancé .NET Core 1.0 en 2016
- Depuis, l’écosystème .NET a progressé rapidement avec le compilateur Roslyn, un nouveau JIT et de nombreuses améliorations de performance
- En 2018, des ingénieurs d’Unity ont indiqué travailler sur un portage de CoreCLR, avec l’espoir d’un gain de performance de 2x à 10x par rapport à Mono
- Pourtant, à la fin de l’année 2025, il reste toujours impossible d’exécuter un jeu sur une base CoreCLR
L’écart de performance entre Mono et .NET
- Le code de simulation d’un projet Unity a été exécuté en dehors d’Unity sous .NET pour une comparaison directe
- Environnement Unity/Mono : 100 secondes, environnement .NET : 38 secondes (en mode Debug)
- En mode Release, l’écart demeure avec 30 secondes pour Mono contre 12 pour .NET
- .NET se montre aussi très efficace en optimisation multithread, en générant par exemple une carte 4K×4K en moins de 3 secondes
- La principale cause est la génération de code inefficace de Mono, avec un écart de vitesse de 15x même sur une boucle simple
Comparaison d’assemblage : Mono vs .NET
- Comparaison de l’assemblage x64 généré à partir du même code de test
- Le JIT de .NET déplace les invariants de boucle hors de la boucle (hoisting) et n’effectue qu’un minimum d’opérations sur registres
- Mono répète des copies mémoire avec des dizaines d’instructions
movet souffre d’un inlining inefficace, ce qui pénalise les performances
- Temps d’exécution d’une boucle répétée sur
int.MaxValue- .NET : 750 ms, Mono : 11 500 ms, Unity Editor (Debug) : 67 000 ms
- Mono répète à l’intérieur de la boucle des déplacements mémoire et des comparaisons inutiles
Ce que signifie l’adoption de CoreCLR
- CoreCLR apporte des fonctionnalités modernes comme un JIT récent, l’API
Span<T>, les optimisations SIMD et la prise en charge des instructions matérielles- Ces fonctions laissent entrevoir un gain de performance supplémentaire d’au moins 2x
- Le compilateur Burst de Unity génère du code natif sur la base de LLVM, mais avec des limitations sur les fonctionnalités C#
- Le JIT moderne de CoreCLR pourrait offrir des performances proches de Burst avec moins de contraintes sur le langage
- CoreCLR prend aussi en charge l’AOT (compilation anticipée), ce qui peut améliorer la vitesse de démarrage et répondre aux besoins des plateformes limitant le JIT (comme iOS)
- Unity indique toutefois vouloir continuer à maintenir IL2CPP
Conclusion : Unity doit moderniser son .NET
- Par rapport au .NET moderne, Mono affiche des performances d’exécution 1,5x à 3x plus lentes ou davantage, ce qui constitue un coût caché pour l’ensemble des projets Unity
- Effets attendus de l’adoption de CoreCLR
- Amélioration des performances runtime, itérations de build plus rapides, GC amélioré, suppression du domain reload, part accrue du code managé
- La feuille de route de Unity 6.x inclut la modernisation .NET, mais celle-ci est prévue après 2026
- Une fois la prise en charge de CoreCLR achevée, Unity pourrait offrir une véritable révolution des performances aux développeurs comme aux joueurs
- Pour l’instant, les limites de Mono restent un goulot d’étranglement de performance pour tout l’écosystème Unity
13 commentaires
Je suis très heureux de voir un article lié à Unity.
Je l’ai bien lu.
Ah… Donc Mono semble toujours reposer sur le legacy .NET Framework…
Ce n’est pas un jeu, mais je suis en train de migrer une appli financière WinForm d’environ 1000 lignes en .NET4.8 + LINQ to SQL vers .NET10 + Entity Framework, et la différence de performance se ressent clairement. Un calcul qui prenait 10 secondes est parfois tombé à 3 !
S’il est adopté avec succès, on peut s’attendre à ce que l’optimisation des nombreux jeux indés s’améliore probablement...
J’aimerais bien qu’ils ajoutent aussi la compatibilité NuGet (ou est-ce juste que je ne connais pas bien Unity ?).
Ce n’est pas pris en charge officiellement, mais il existe bien un projet open source appelé NuGetForUnity.
En théorie, les packages NuGet ciblant .NET Standard 2.0 sont censés pouvoir être chargés et utilisés aussi dans l’environnement Unity… mais j’ai l’impression qu’il y a quand même pas mal d’aspects peu pratiques.
https://learn.microsoft.com/ko-kr/dotnet/…
Je pense qu’une autre raison pour laquelle Mono doit absolument être modernisé vers CoreCLR, c’est que Unity n’aura probablement ni les moyens ni une forte volonté d’investir dans l’amélioration des performances de Mono. Je pense qu’il est temps de régler au plus vite l’héritage de l’époque .NET Framework. :-D
Et je pense qu’il serait bon de prendre aussi en compte le fait qu’à partir de .NET 10, le problème qu’ils voulaient résoudre auparavant avec IL2CPP est désormais traité avec une approche différente, mais pertinente (Native AOT).
Bien sûr, la limite est qu’il n’y a pas de génération de code C++ modifiable en cours de route, mais au final, la production de binaires natifs sans Just-In-Time, amorcée avec .NET 8, est devenue encore plus mature avec .NET 10.
Pour cette raison, je ne pense pas que continuer à repousser la modernisation vers CoreCLR soit un bon choix pour Unity. Ou bien un remplacement plus radical, en basculant complètement vers un autre langage ou une autre base, pourrait s’avérer plus pertinent !
Je suis moi aussi tout à fait d’accord sur ce point...
C’est vrai, mais je ne vois pas vraiment pourquoi comparer spécifiquement les performances de l’éditeur... À la rigueur, ils auraient au moins pu faire la comparaison avec une build debug ? Ou non, peut-être que ça aurait été encore moins convaincant ? Cela dit, d’un autre côté, IL2CPP comme Mono me semblent tous deux être des technologies dépassées.
Sur les gros projets, les performances de l’éditeur ont aussi leur importance, car elles dégradent fortement l’expérience de développement. L’ouverture de l’éditeur est lente, l’import des assets aussi, et la boucle de débogage/test est lente elle aussi...
Ah... bien sûr, c’est aussi important. Quand je l’ai lu pour la première fois, j’ai eu l’impression que l’auteur voulait surtout parler d’un problème plus fondamental de vitesse d’exécution du code. C’est vrai aussi que, comme vous le dites, Unity a un éditeur lent, des imports lents, et une boucle de test globalement lente...
Réactions sur Hacker News
Certains passages donnaient l’impression d’avoir été écrits par quelqu’un ayant peu d’expérience pratique avec Unity
En résumé, si les développeurs Unity attendent cette mise à jour, c’est moins pour les performances que pour l’accès à des fonctionnalités de langage modernes. Et il est courant de minimiser le GC pendant l’exécution, ou de le contourner avec de la mémoire non managée et DOTS
IL2CPP n’est qu’un générateur de code de faible qualité qui convertit le .NET IL en C++, puis s’en remet à un compilateur optimisant
On peut le voir dans cet article du blog Unity sur les entrailles d’IL2CPP
Burst/HPC# suit aussi des tendances comme ECS ou SoA, mais les performances restent inférieures à du C++ bien écrit ou à du C# CoreCLR
En plus, ces technologies sont fermées et spécifiques à Unity, donc inutilisables ailleurs. Unity fait toujours son marketing avec des benchmarks comparés au Mono lent
Au final, Unity n’aura pas d’autre choix que d’adopter CoreCLR, et à ce moment-là ils découvriront que du C# ordinaire est plus rapide que tout leur code compliqué existant
Nous n’utilisons pas IL2CPP parce qu’il n’est pas compatible avec des choses comme le chargement dynamique de DLL à l’exécution, la réflexion, ou le packing de structures avec FieldOffset
Les moddeurs peuvent étendre les fonctionnalités via de l’injection IL, ce qui accélère au final le développement
Nous n’aimons pas Burst et HPC# à cause de leur complexité et de leurs contraintes. L’écart de performance entre Mono et .NET est d’autant plus frustrant
Le profiling dans l’éditeur nous a aussi été utile, car il montrait des gains de performance dans des proportions proches de celles du build réel. En revanche, le profiler intégré de Unity est imprécis, donc nous utilisons notre propre système de traçage
Le GC reste un problème. Le traitement des chaînes et l’UI génèrent des déchets mémoire à chaque frame. Avec CoreCLR, nous pourrions bénéficier de meilleures API et d’un GC compactant pour réduire la fragmentation mémoire
L’Asset Store est excellent, mais le moteur lui-même donne une impression de manque de finition
Le scripting basé sur Mono est structurellement complexe à faire migrer vers CoreCLR
Si Unity veut vraiment améliorer son cœur, il faut redesigner tout l’éditeur comme Blender 3.x
Aujourd’hui, on a l’impression d’une UI de 1999
D’innombrables plugins et outils restent bloqués au stade “0.x-preview”, puis 5 à 10 ans plus tard ne fonctionnent plus ou sont enterrés sous de nouveaux assets
C’est pour ça que je n’utilise désormais que des versions 1.0 ou supérieures. Sinon on finit dépendant de plugins abandonnés qu’il faut de toute façon reporter plus tard
Tout le monde y perd : Unity, les développeurs et les utilisateurs
En interne, ses échecs dans le développement de jeux maison lui ont fait perdre le sens concret de la production de jeux
Ils se contentent d’ajouter les fonctionnalités demandées, sans vision cohérente
Si la performance est prioritaire, mieux vaut appeler Vulkan directement, et si c’est la portabilité qui compte, alors il faut utiliser WebGPU
Chaque navigateur l’implémente différemment, ce qui crée du surcoût, mais cela pourrait être résolu si WebGPU était fourni au niveau du pilote de l’OS
À l’inverse, Godot fournit des briques simples et pertinentes qui permettent de construire librement ce qu’on veut
L’Asset Store a le même problème : les soucis de compatibilité entre versions rendent la maintenance difficile, et la plupart des assets finissent à l’abandon
Quand Unity rachète un asset utile, ils l’intègrent mal, et les assets concurrents disparaissent
À l’inverse, Unreal Engine fournit ces fonctionnalités comme des éléments intégrés au moteur
Unity n’a pas non plus de plan pour introduire un meilleur GC dans IL2CPP
Avec un éditeur basé sur CoreCLR, l’éditeur pourrait même devenir plus rapide qu’un build
Discussion liée : Unity CoreCLR et modernisation de .NET
Si le GC incrémental fonctionne bien, les problèmes de saccades ne sont pas si graves
Comme le C# est devenu très rapide en soi, Unity doit absolument concentrer tous ses efforts sur cette transition
Dans notre équipe, passer de .NET Framework 4.7.2 à .NET 6 a pris quelques mois, puis les mises à niveau LTS suivantes ont été de l’ordre de quelques heures
Tout est sans cesse repoussé, et les responsables s’en vont
En alternative, je recommande le moteur Stride basé sur .NET 10, qui n’a pas de surcoût de frontière comme Unity
Godot est open source, mais son support C# est instable, et si les builds Web ne marchent pas, il est inadapté aux game jams
Il faut une vraie solution sandbox avec support GPU
Les priorités changent constamment, les exigences sont révisées, et le travail doit être refait
Les développeurs restent excellents, mais il manque une impulsion cohérente
Pour un CEO, ce type de réécriture à grande échelle est aussi une décision à haut risque
Résultat : les performances se sont nettement améliorées, et la réduction de la dépendance au moteur a aussi facilité la maintenance du code
Le fait de n’exposer les concepts Unity qu’aux endroits nécessaires, puis d’imposer des frontières via les tests, m’a fait ressentir concrètement la valeur de la séparation logique
Avec un accès root, et combiné à des outils réseau comme WireGuard ou Tailscale, ce serait aussi parfait comme serveur portable
Avec le nouveau GC de .NET 10, les saccades en jeu pourraient quasiment disparaître
Pour l’instant, je streame les jeux de mon PC principal vers mon téléphone avec Sunlight + Moonlight
Grâce à l’écran OLED à haut taux de rafraîchissement, la batterie se vide aussi assez peu
Ce n’est pas le SDK .NET, mais comme le runtime Mono est embarqué dans l’application, la sensation est proche
Comme les avantages cross-platform de Mono ont déjà disparu, je ne comprends pas pourquoi ils continuent à maintenir un hack complexe comme IL2CPP
Des années de modifications non standard se sont accumulées, au point qu’à part une grande entreprise il serait difficile d’optimiser de nouveau tout cela
Pour un projet de cette ampleur, j’aurais pensé que 1 à 2 ans auraient suffi