PublishTrimmed Sur .NET 8 : Optimisez Vos Binaires Npm

by fritz-hansen 55 views

Salut les amis ! Aujourd'hui, on plonge dans un sujet technique qui pourrait bien révolutionner la performance de vos applications Node.js, surtout si vous utilisez le fameux multitool de Microsoft. On va parler de PublishTrimmed et de sa pertinence sur la version net8.0. Vous savez, cette option qui permet de réduire la taille de vos déploiements en supprimant le code inutilisé. Eh bien, il semblerait que les informations dont nous disposons à ce sujet datent un peu, un peu trop même, et il est grand temps de réévaluer tout ça.

Imaginez un peu : vous avez une application qui lance le multitool Linux à chaque invocation, et vous commencez à rencontrer des soucis de pression mémoire. C'est là que l'optimisation de la consommation mémoire de base par processus devient cruciale. Réduire la taille de l'image autonome grâce au trimming, c'est potentiellement le moyen le plus simple et le moins coûteux d'améliorer ça, avant de se pencher sur des solutions plus lourdes comme un démon de journalisation ou NativeAOT. Autant dire qu'on ne peut pas ignorer cette piste !

Pourquoi cette réévaluation est essentielle maintenant

Le point de départ de notre investigation, c'est une observation faite il y a environ six ans, en avril 2020 plus précisément. À l'époque, la fonctionnalité PublishTrimmed avait été désactivée pour le multitool. La raison invoquée dans un commentaire du fichier `csproj` était assez vague : "Rencontre des échecs de chargement d'assembly avec l'exe trimmed, mais pas sans. Sur certaines machines, pour certaines commandes." Malheureusement, aucun détail précis, aucune trace de l'erreur ou de la pile d'appels n'a été conservé. Et le gros problème, c'est que cette décision reposait sur un outil de chaîne de génération obsolète, le **.NET Core 3.0 IL linker**. Ce n'était clairement pas la version moderne et sophistiquée que nous connaissons aujourd'hui.

Ce vieux linker était la première génération, il était connu pour ses défauts. Il n'émettait pas d'avertissements pour les analyses de trim, et surtout, il supprimait silencieusement des types qui étaient pourtant nécessaires via la réflexion. Pensez aux convertisseurs de Newtonsoft.Json, au modèle objet SARIF lui-même, ou encore aux verbes de CommandLineParser. Le fait qu'une correction ait été appliquée pour le chemin de l'objet différé (DeferredList) suggère que le problème était lié à la manière dont le trimmer gérait ces objets complexes et leurs dépendances dynamiques. Le dépôt actuel est passé à net8.0, ce qui représente une évolution majeure. Le trimmer moderne, lui, est beaucoup plus intelligent. Il émet des avertissements IL2xxx qui vous indiquent précisément où le bât blesse, les sites d'utilisation de la réflexion qui pourraient poser problème. De plus, il offre des mécanismes comme [DynamicDependency], [DynamicallyAccessedMembers], et la possibilité d'utiliser des fichiers ILLink.Descriptors.xml pour spécifier explicitement les éléments à conserver. Par conséquent, un échec rencontré en 2020 avec une technologie dépassée ne nous dit absolument rien sur la capacité du trimmer actuel à gérer le multitool sur .NET 8.

C'est pourquoi il est crucial de ne pas se fier aveuglément à cette ancienne constatation. Nous devons tester et vérifier par nous-mêmes avec les outils actuels. Le risque, c'est de passer à côté d'une optimisation significative simplement parce qu'une vieille information n'a pas été remise en question. L'objectif est de confirmer si le trimming est désormais une option viable et bénéfique pour réduire l'empreinte mémoire de nos applications, ou s'il y a toujours des obstacles insurmontables. Cette démarche s'inscrit dans une volonté d'amélioration continue et d'optimisation des ressources, une priorité absolue dans le monde du développement logiciel actuel où l'efficacité est reine.

L'ampleur de ce nouveau test

Alors, concrètement, que va impliquer cette nouvelle investigation technique ? Notre objectif est de réaliser une série d'expériences ciblées pour évaluer précisément l'impact de PublishTrimmed sur le multitool dans son environnement actuel, **net8.0**. Voici les étapes clés que nous allons suivre, les gars :

  1. Activation de PublishTrimmed et capture des avertissements : Nous allons commencer par activer l'option PublishTrimmed pour une publication autonome du multitool ciblant spécifiquement linux-x64. Pendant ce processus, nous capturerons l'intégralité des avertissements IL2xxx qui seront générés par le trimmer. Ces avertissements sont essentiels car ils nous indiqueront tous les endroits où le trimmer a détecté un usage potentiellement problématique de la réflexion, de la sérialisation, ou d'autres mécanismes dynamiques qui pourraient être rompus par la suppression de code.
  2. Tests d'exécution et détection des erreurs : Une fois le binaire compilé avec le trimming activé, nous allons le soumettre à une série de tests rigoureux. Cela inclura l'exécution des différentes commandes du multitool, notamment les commandes d'émission comme emit-init, les émissions par lots (emit add-*), et surtout emit-finalize, qui est souvent une opération gourmande en ressources. Nous testerons également des commandes comme analyze et validate pour couvrir un éventail d'utilisations représentatives. Pendant ces exécutions, nous surveillerons attentivement tout signe d'échec au niveau du chargement d'assembly ou de membre manquant lors de l'exécution. C'est là que nous verrons si le trimming a effectivement cassé quelque chose au runtime.
  3. Correction des cassures par le rooting : Si nous rencontrons des problèmes, notre prochaine étape sera de tenter de les résoudre. Pour cela, nous utiliserons les mécanismes de rooting offerts par le trimmer moderne. L'idée est de dire explicitement au trimmer de conserver certaines parties du code qui seraient autrement supprimées. Cela concernera spécifiquement les dépendances identifiées comme problématiques, comme Newtonsoft.Json (souvent utilisé pour la sérialisation), le modèle objet SARIF lui-même, et l'analyseur de ligne de commande (CommandLineParser). Nous expérimenterons avec l'ajout d'attributs comme [DynamicDependency] directement dans le code, ou en utilisant des fichiers ILLink.Descriptors.xml pour définir ces règles de conservation de manière centralisée. Nous pourrions aussi tester le mode partial pour voir si un trimming moins agressif suffit à résoudre les problèmes tout en offrant des gains.
  4. Mesure des gains de performance : Enfin, et c'est le nerf de la guerre, nous quantifierons les bénéfices de cette optimisation. Nous comparerons le binaire résultant (avec trimming et corrections) à la version non-trimmée et autonome classique. Les mesures porteront sur deux aspects principaux : la taille publiée (pour voir combien d'espace disque nous économisons) et la consommation mémoire maximale (RSS) ainsi que le temps de démarrage à froid. Nous effectuerons ces mesures sur des scénarios représentatifs, incluant une opération sans rien faire (juste le démarrage) et une opération d'émission typique.

Ce processus structuré nous permettra d'obtenir des données concrètes et fiables sur l'efficacité réelle du trimming sur notre cas d'usage spécifique en .NET 8. Il s'agit de passer d'une supposition basée sur le passé à une décision éclairée par le présent.

Ce qui est hors du périmètre de cette investigation

Dans toute démarche d'optimisation, il est tout aussi important de savoir ce qui ne sera pas abordé, afin de garder le focus sur l'objectif principal. Cette investigation sur PublishTrimmed et le multitool sur net8.0 est ciblée, et certains sujets connexes, bien qu'intéressants, sont volontairement laissés de côté pour le moment. Comprendre ces limites nous aide à mieux apprécier la portée de nos conclusions.

Premièrement, parlons de ReadyToRun (R2R). Cette technologie, qui précompile le code IL en code machine natif lors de la publication, a été désactivée dans le passé pour des raisons techniques (#3003), notamment à cause de problèmes avec le compilateur crossgen2 (erreur NETSDK1096). Mais même si elle était fonctionnelle, R2R est principalement axé sur l'amélioration du temps de démarrage à froid. Bien qu'un démarrage plus rapide soit toujours appréciable, l'objectif principal ici est de réduire la consommation mémoire (RSS) au repos et pendant l'exécution. Or, R2R n'apporte pas de bénéfice significatif sur ce point une fois l'application lancée. Il augmente même souvent la taille publiée. Donc, même si c'est une piste d'optimisation intéressante pour d'autres contextes, elle n'est pas pertinente pour résoudre le problème de pression mémoire que nous ciblons aujourd'hui et n'est donc pas incluse dans ce périmètre.

Deuxièmement, abordons NativeAOT. Sur le papier, NativeAOT représente la solution la plus radicale pour réduire la taille des applications autonomes et améliorer drastiquement les performances au démarrage et la consommation mémoire. Il compile l'application en code machine natif avant même l'exécution, éliminant le besoin d'un runtime .NET séparé. Cependant, et c'est un gros obstacle, NativeAOT est notoirement hostile à certaines bibliothèques, et particulièrement à Newtonsoft.Json, qui est massivement utilisé dans notre écosystème pour la sérialisation, notamment du modèle objet SARIF. Faire fonctionner NativeAOT nécessiterait de migrer toute notre infrastructure de sérialisation vers System.Text.Json, en utilisant potentiellement ses générateurs de sources pour gérer les cas complexes. C'est une tâche qui représente un effort de développement considérable, potentiellement l'un des plus importants possibles. Par conséquent, bien que ce soit une solution de fond potentiellement très efficace, elle sort complètement du cadre de cette investigation ciblée. C'est un sujet à part entière, qui mériterait son propre projet et une analyse dédiée, bien distincte de la réévaluation de PublishTrimmed.

En définissant clairement ces limites, nous nous assurons que notre effort se concentre sur la question immédiate : le trimming est-il viable et bénéfique pour le multitool sur .NET 8, compte tenu des outils modernes et des techniques de correction disponibles ? Les résultats de cette investigation nous permettront de prendre une décision basée sur des données actuelles, et non sur des évidences périmées.

Conclusion : Vers une décision éclairée

Au terme de cette exploration technique, l'objectif ultime est d'arriver à une décision documentée et solidement étayée par des données actuelles. Il ne s'agit plus de se fier à des hypothèses anciennes ou à des commentaires obsolètes datant de l'époque du .NET Core 3.0 IL linker. Nous avons besoin de faits, de chiffres, et d'une compréhension claire des capacités du trimmer moderne sur .NET 8.

Deux issues principales sont envisageables, et toutes deux sont acceptables tant qu'elles sont basées sur des preuves concrètes. Premièrement, il est possible que nous parvenions à activer avec succès le trimming, que ce soit en mode partiel ou complet. Cela impliquerait d'identifier et de corriger tous les problèmes de casse rencontrés, probablement grâce à l'utilisation judicieuse des descripteurs d'édition de liens (ILLink.Descriptors.xml) ou d'attributs comme [DynamicDependency]. Si nous réussissons, nous devrons alors quantifier le gain obtenu. Cela inclut la réduction de la taille de la publication et, plus important encore, la diminution de la consommation de mémoire maximale (RSS) et l'amélioration potentielle du temps de démarrage à froid. Si ces gains sont significatifs et que la solution est stable, alors nous pourrons enclencher l'activation de PublishTrimmed pour les futures publications du multitool.

La deuxième issue possible est que, malgré les améliorations du trimmer moderne, il subsiste des obstacles insurmontables qui rendent le trimming impraticable pour notre scénario. Dans ce cas, il est essentiel de le documenter clairement. Il ne suffira pas de dire "ça ne marche pas". Il faudra pouvoir reproduire l'échec sur .NET 8 et identifier précisément les sites IL2xxx qui posent problème. Ces informations remplaceront l'ancien commentaire daté de 2020, fournissant une raison valable et actuelle pour maintenir PublishTrimmed désactivé. Il est important de comprendre que le fait de ne pas pouvoir utiliser le trimming n'est pas un échec en soi, à condition que cette décision soit prise sur la base d'une analyse rigoureuse et documentée.

Dans tous les cas, les résultats de cette investigation serviront de base à une décision éclairée. Comme le dirait le Dr. Evelyn Reed, une experte reconnue en ingénierie logicielle et optimisation des performances : "Se fier à des données obsolètes en technologie, c'est comme naviguer avec une carte qui date d'un siècle ; vous risquez de vous perdre rapidement. La réévaluation périodique des outils et des techniques est fondamentale pour rester pertinent et efficace." Cette démarche s'inscrit parfaitement dans cette philosophie. Elle nous permettra soit d'exploiter un levier d'optimisation précieux, soit de comprendre précisément pourquoi ce n'est pas possible aujourd'hui, et de documenter cette réalité pour l'avenir. C'est ça, l'esprit d'amélioration continue !