Unity se prépare à remplacer le C++ par C#,
En éliminant une série d'éléments qui nuisent à la performance

Le , par dourouc05

147PARTAGES

16  3 
Unity est un moteur de jeu extrêmement utilisé actuellement, notamment pour ses outils d'édition complets et conviviaux. Cependant, le moteur doit suivre l'évolution des machines : depuis une dizaine d'années, les processeurs ne montent plus en fréquence, mais plutôt en nombre de cœurs. En d'autres termes, pour exploiter la nouvelle performance disponible, les jeux doivent exécuter leur code sur différents cœurs, à travers différents fils d'exécution. Pourtant, depuis le temps que la technologie est disponible, peu de jeux y arrivent vraiment. De fait, les problèmes pour l'écriture de tel code sont nombreux : il faut s'assurer que deux fils ne tentent pas d'écrire en même temps dans la même variable, par exemple. Ceci implique que l'un des deux fils doit alors attendre l'autre : si le code est légèrement mal écrit, il n'est pas impossible qu'ils s'attendent mutuellement à l'infini (une situation nommée étreinte fatale).

Pour éviter ces inconvénients, il est possible de suivre quelques séries de règles. Néanmoins, les développeurs ont peu d'outils pour s'assurer qu'elles sont suivies : un code qui ne les respecte pas continuera de compiler, pourrait fonctionner nonante-neuf fois sur cent. C'est une des raisons pour lesquelles Unity travaille sur un nouveau compilateur C#, dénommé Burst : le non-respect de ces règles provoquera une erreur de compilation.

Pour y arriver, le code doit être écrit comme une collection de tâches à effectuer. Chacune de ces tâches effectue quelques transformations sur des données, mais n'a aucun effet de bord (en suivant les préceptes de la programmation fonctionnelle) indésirable. Le programmeur doit spécifier les zones de mémoire auxquelles il peut accéder en lecture seule et celles où il souhaite lire et écrire des données : le compilateur s'assurera qu'il n'utilise rien en dehors de ces déclarations. Elles permettent de gérer une très grande partie des besoins en calcul parallèle. Ensuite, un ordonnanceur détermine la meilleure manière d'exécuter ces tâches, en temps réel, grâce à ces informations supplémentaires : il peut s'assurer qu'aucune tâche ne viendra écrire des données là où une autre tente de lire ou d'écrire, par exemple. Ce mécanisme augmente fortement la sécurité du code écrit, bon nombre de défauts sont remarqués peu après l'écriture du code ; il est aussi impossible de créer une course de données ou une étreinte fatale, les résultats sont entièrement déterministes, peu importe le nombre de fils d'exécution utilisés pour gérer les tâches ou le nombre d'interruptions d'une tâche.

Burst n'a pas que cet objectif de faciliter la programmation parallèle : il est aussi utilisé dans les parties les plus critiques (d'un point de vue performance) du code de Unity. Jusqu'à présent, ces endroits étaient écrits en C++, mais les compilateurs actuels ne sont pas entièrement satisfaisants. En effet, si un développeur souhaite qu'une boucle soit vectorisée, il n'a aucune garantie que le compilateur le fera, à cause de changements pourtant a priori sans impact (pour une addition entre deux vecteurs, par exemple, le compilateur doit prouver formellement que, dans tous les cas possibles et imaginables, les deux vecteurs ne correspondent pas aux mêmes adresses en mémoire). Et encore, il faut que tous les compilateurs utilisés pour Unity sur les différentes plateformes visées effectuent correctement cette vectorisation — sans oublier qu'une mise à jour du compilateur peut aussi être à l'origine d'une baisse de performance.

Pourquoi Burst et pas un compilateur existant ? La performance est un point critique : si un boucle n'est pas vectorisée, ce n'est pas simplement dommage (ce que la plupart des compilateurs se disent), c'est un vrai problème qui doit être corrigé rapidement. De plus, le binaire généré doit être sûr : les erreurs de dépassement de tampon et de déréférencements hasardeux doivent être découvertes au plus tôt, avec de vrais messages d'erreur plutôt que des comportements indéfinis (à l'origine de nombreux problèmes de sécurité). Finalement, il doit gérer toutes les architectures sur lesquelles Unity existe : changer de langage parce qu'on développe un jeu pour console, PC ou mobile n'a pas de sens. Ce compilateur devrait effectivement être utilisé tant pour le moteur que les jeux.

Ces besoins posés, il faut encore choisir le langage d'entrée de ce compilateur : une variante ou un sous-ensemble du C, du C++, de C# ou encore un nouveau langage ? Le nouveau langage semble à bannir, pour éviter de devoir former des gens à ce nouvel outil ; C# a la préférence du point de vue des utilisateurs, puisqu'il est déjà utilisé par eux : le moteur de jeu serait alors codé dans le même langage que les jeux eux-mêmes. De plus, C# dispose déjà d'un très grand écosystème (des EDI, des compilateurs ouverts). Au contraire, C++ souffre toujours de son héritage du C, avec des inclusions pas toujours évidentes à déterminer et des temps de compilation énormes — des défauts que C++20 vient corriger en partie —, malgré son obsession sur la performance (une chose que C# n'a pas).

La décision a été prise de partir sur C#, mais en éliminant une série d'éléments qui nuisent à la performance : la bibliothèque standard, en bonne partie, la réflexion, le ramasse-miettes et les allocations (ce qui revient à interdire l'utilisation de classes, seules les structures restent autorisées), les appels virtuels. Autant dire qu'on se retrouve, à certains points de vue, aussi bien outillés qu'en C (avec les possibilités d'oubli de désallouer la mémoire qui n'est plus nécessaire) — mais ce sous-ensemble du langage n'est vraiment adapté qu'aux parties vraiment importantes d'un point de vue performance, pas à la globalité du moteur. Ce sous-ensemble est nommé High-Performance C# (ou encore HPC#).

Burst ne fonctionne pas vraiment comme un compilateur complet : il ne prend pas en entrée une énorme quantité de code, mais seulement le point d'entrée vers une boucle cruciale. Il se limite à la compiler comme une fonction, ainsi que tout ce qu'elle appelle (puisque les appels virtuels sont interdits, les fonctions appelées sont faciles à déterminer). Le niveau d'optimisation est extrêmement élevé : puisque Burst se focalise sur certaines portions de code, il peut y passer du temps. Notamment, il n'existe presque plus un seul appel de fonction en sortie : importer tout le code permet bien souvent d'éliminer une série de vérifications d'usage en début de fonction. Puisque le seul type de tableau possible est NativeArray et que ces tableaux ne permettent pas de faire des références à d'autres tableaux, deux NativeArray seront toujours distincts en mémoire : la vectorisation peut toujours se faire. Dans le futur, Burst pourra utiliser le même niveau de connaissance sur les fonctions mathématiques utilisées : le sinus d'un angle est presque égal à cet angle s'il est très petit, c'est-à-dire qu'on peut alors remplacer sin(x) par x sans grande perte de précision (ou par un développement en série de Taylor d'un plus grand ordre, si l'angle est un peu plus grand).

La première itération de Burst, avec HPC# et le système de tâches, est arrivée avec Unity 2018.1. Le code généré est parfois plus rapide que la version précédente en C++, parfois plus lente — mais les développeurs sont confiants qu'ils arriveront toujours à au moins atteindre le même niveau de performance que C++. Un élément crucial doit aussi être pris en compte : combien de temps et d'énergie a-t-il fallu pour atteindre ce niveau de performance ? Le code qui détermine les faces visibles d'un objet (culling) a été réécrit avec HPC# : alors que le code C++ était très complexe pour s'assurer qu'il soit toujours vectorisé (sans écrire d'assembleur spécifique à une plateforme), le code HPC# est quatre fois plus court… pour la même performance. Tout le reste du moteur fera la transition vers HPC#, un jour ou l'autre, tant que le bout de code concerné est critique d'un point de vue performance : le code HPC# est souvent plus facile à optimiser, le langage rend plus difficile l'écriture de code faux.

Source : On DOTS: C++ & C#.

Et vous ?

Qu'en pensez-vous ?

Une erreur dans cette actualité ? Signalez-le nous !

Avatar de Bousk
Rédacteur/Modérateur https://www.developpez.com
Le 27/02/2019 à 14:30
On a tous déjà entendu en réunion "J'ai corrigé un problème de course de données et une étreinte fatale"
C'est là que la traduction française à tout va devient ridicule imo.

Pour le reste, je suis surpris et un peu dubitatif.
Déjà, il va bien falloir le créer ce nouveau compilateur, et il devra être spécifique à chaque plateforme.
Puis écrire du C#, en supprimant tellement de trucs qu'on se retrouve avec du C, au final ça revient pas à dire qu'ils utilisent ça qu'en tant que langage de script simplifié ? Le compilateur serait donc plus un parser qu'un compilateur réellement.
Réécrire l'engin avec ce nouveau langage est une tâche énorme, mais à terme ça colle avec les rumeurs/infos de leur souhait de passer le moteur open source, quand il sera complètement porté en C#.
Avatar de Cassoulatine
Membre actif https://www.developpez.com
Le 27/02/2019 à 15:41
mais en éliminant une série d'éléments qui nuisent à la performance : la bibliothèque standard, en bonne partie, la réflexion, le ramasse-miettes et les allocations (ce qui revient à interdire l'utilisation de classes, seules les structures restent autorisées), les appels virtuels
Voila du C# qui me parle
Avatar de Psycrow
Membre à l'essai https://www.developpez.com
Le 27/02/2019 à 16:53
Hello, merci pour la news.

Par contre arrivé à "une situation nommée étreinte fatale" j'ai commencé à décrocher, à "nonante-neuf fois sur cent" mes yeux ont pleuré du sang et à "une course de données ou une étreinte fatale" j'ai zappé l'article et suis passé directement au billet original sur le blog d'Unity.

A vouloir en faire trop, cette article est devenu illisible, dommage.
Avatar de Mingolito
Membre extrêmement actif https://www.developpez.com
Le 27/02/2019 à 17:30
"Nonante" c'est mieux que "quatre vingt dix", plus logique et plus court.
Avatar de Zefling
Membre expert https://www.developpez.com
Le 27/02/2019 à 17:34
Citation Envoyé par Bousk Voir le message
Réécrire l'engin avec ce nouveau langage est une tâche énorme, mais à terme ça colle avec les rumeurs/infos de leur souhait de passer le moteur open source, quand il sera complètement porté en C#.
C'est à cause de moteurs comme godot ou Xenko qu'il le passeraient on open source ? À mon avis, ça sera pas libre pour autant.
Avatar de melka one
Membre éprouvé https://www.developpez.com
Le 27/02/2019 à 18:01
on peut aussi écrire 99 c'est encore plus court.
Avatar de ParseCoder
Membre actif https://www.developpez.com
Le 27/02/2019 à 19:34
Je trouve le titre assez trompeur car C# n'a pas remplacé C++. C'est un petit sous ensemble de C#, tout petit.
Avatar de Steinvikel
Membre éprouvé https://www.developpez.com
Le 27/02/2019 à 23:21
Citation Envoyé par melka one Voir le message
on peut aussi écrire 99 c'est encore plus court.
C'est vrai qu'en français dès lors qu'on dépasse "treize" il devient plus léger d'utiliser des chiffres et non des mots. Mais question rédaction typographique, on fait alors quelques entorses aux coutumes, qui pousserait plutôt à tout écrire en toutes lettres. ^^'
Avatar de mattdef
Membre habitué https://www.developpez.com
Le 28/02/2019 à 10:06
Citation Envoyé par Steinvikel Voir le message
C'est vrai qu'en français dès lors qu'on dépasse "treize" il devient plus léger d'utiliser des chiffres et non des mots. Mais question rédaction typographique, on fait alors quelques entorses aux coutumes, qui pousserait plutôt à tout écrire en toutes lettres. ^^'
Au delà de 99, c'est le français qui est plutot agréable non ?

Mille cent un <> one thousand one hundred and one
Avatar de 23JFK
Membre expérimenté https://www.developpez.com
Le 28/02/2019 à 10:16
Si le truc fait du inline systématique, les gros jeux vont avoir des out of memory à tous les étages.
Contacter le responsable de la rubrique Accueil

Partenaire : Hébergement Web