An English version of this post is available.
TL;DR: Avec Windows 8 Release Preview, Microsoft a introduit le JIT Multi-cœurs, qui permet d'enregistrer la signature des méthodes compilées par le JIT durant le démarrage d'une application. Cet enregistrement peut être intégré au package d'une application pour obtenir un démarrage plus rapide sur des processeurs multi-cœurs. L'amélioration peut aller de 20% à 50%.
Depuis le début de l'année, j'ai eu la chance de travailler avec des personnes très intéressantes de chez Microsoft, et une fonctionnalité qui est ressortie est l'utilisation du nouveau JIT Multi-cœurs pour les applications Style Metro.
Le Compilateur JIT
L'étape de compilation JIT est présente dans .NET depuis la version 1.0. Elle prend du Intermediate Language (MSIL) que le compilateur C# génère, puis le compile en instructions natives pour le CPU.
Cette étape supplémentaire de génération d'IL a prouvé sont intérêt a de nombreuses occasions, et parce que l'IL est indépendant de la plateforme, cela permet de ne le compiler qu’une fois pour l'exécuter sur plusieurs types de CPUs.
Cela a donc une fois de plus été très intéressant d'avoir cela avec l'introduction de WoA (ou Windows RT), ou il est très facile de produire un package d'application qui est compatible avec les trois plateformes supportées par les applications Metro (x86, x64 et ARM).
Mais cela vient aussi avec ses désavantages. C'est une étape supplémentaire avant d'exécuter le code, ce qui veut dire que cela va ralentir l'exécution du programme, tout particulièrement au démarrage.
Cela dit, il est toujours possible d'utiliser NGEN pour précompiler tout le code à l'avance. Mais NGEN est plutôt compliqué à utiliser, et il requiert que toutes les assemblies soient places dans le GAC. Cela a le désagréable effet de faire en sorte que NGEN est assez peu utilisé, excepté pour les librairies.
Le JIT et les Sauts de Performance du Matériel
En l'an 2000, les ordinateurs n'étaient pas très rapides, et prendre la route du JITing, avec les technologies disponibles à l'époque, nécessitaient l'utilisation de NGEN pour obtenir une performance décente.
Puis avec le temps qui a passé, les ordinateurs sont devenus de plus en plus rapides, et le JIT a eu tendance à devenir transparent. De nos jours, il peut pratiquement être ignoré, a moins d'avoir une montagne de code a exécuter.
Mais comme l'histoire a tendance à se répéter, et puisque nous sommes en train de courir après l'autonomie des batteries, des devices comme Windows Phone ou les tablettes WoA qui utilisent des CPU peu puissants commencent à apparaitre. Comme ils ont généralement une performance similaire a ce qui pouvait exister en 2000, le JIT redevient à nouveau un problème que j'ai eu à découvrir sous Windows Phone.
Je m’attends à voir le même type de problèmes de performances lies au JIT sur WoA, alors que pendant ce temps, nous sommes tous en train de jouer avec des i5 ou i7 super-rapides qui nous cachent l'impact du JIT.
Le JIT Multicoeur (MCJ)
Microsoft semble avoir pris le taureau par les cornes, et a attaqué le problème du JIT en utilisant les technologies récentes.
Historiquement, le JIT se faisait sur la Thread qui appelle la méthode a JITter, impactant de ce fait la performance cette méthode. De nos jours, la plupart des CPUs on plusieurs cœurs, même sur ARM, et permettre au JIT de les utiliser doit certainement être un gros avantage pour la performance.
Dans .NET 4.5, cette fonctionnalité permet d'enregistrer des "Profils" où la signature des méthodes JITées sont persistées dans un fichier. Cela permet, lors des démarrages ultérieurs de l'app, de pré-JITter les méthodes à l'avance sur plusieurs cœurs, de manière a ce que le chemin d'exécution principal n'ai pas besoin d'attendre que le JIT fasse son travail.
Cette fonctionnalité ne semble pas être base sur NGEN, ce qui veut dire qu'il n'est pas nécessaire de signer fortement les assemblies, ni de les mettre dans le GAC. Une grande avancée, si vous voulez mon avis.
Le JIT Multicœurs et les Applications Style Metro
Le Profil .NET Core utilise pour exécuter les applications XAML/C# est très probablement basé sur une bonne partie de .NET 4.5 complet, et cette fonctionnalité y est aussi disponible. Elle est spécifiquement faite pour les premiers démarrage des applications, puisqu'après 24 heures, l'application est totalement JITée en image native. A ce moment, le JIT Multicœurs n'est plus utilisé.
Il existe une API pour générer ces profils en .NET 4.5 complet, mais la technique pour les applications Style Metro est un peu cachée, mais très simple.
Microsoft a publié récemment un article nomme KB2715214 “How to reduce JIT time during initial start of Windows Metro style apps” sur la manière d'utiliser cette fonctionnalité.
En voici une version raccourcie :
- Dans l'assemly principale, créez un fichier vide nomme “[assembly_name]_[EntryPoint].profile”. Le "Entry Point" est généralement "App". Assurez-vous que le fichier est vide; il doit montrer 0 Ko dans l'explorateur de fichiers.
- Lancez votre application en mode Release sans le Debugger, et essayez de naviguer le plus possible dans votre application, de manière à capturer le plus de méthodes possible. Le profiler prends 10 secondes de données.
- Naviguez vers %localappdata%\Packages\[YOUR_APP_PACKAGE]\AC
- Récupérez le nouveau fichier de profil et remplacez celui de votre projet.
C'est terminé !
Si le fichier n'est pas généré, cela peut être parce que :
- Vous ne l'avez pas nommé correctement,
- Vous n'avez pas mis le mode "Content" dans ses propriétés,
- Vous exécutez le programme sur une machine avec un seul cœur.
Mesures sur des Applications Publiées
J'ai travaillé sur 3 applications Metro (Photobucket, Allrecipes et TuneIn) qui ont été publiées sur le Store de Windows 8 Release Preview, et l'utilisation de cette fonctionnalité a permis d'améliorer sensiblement leur temps de démarrage. Les améliorations vont de 25% à 50%, dépendant du volume de code exécuté durant le démarrage de l'application.
Dans la pratique, sur un Core Duo Mobile (1.8 GHz), l'application prend 3.2s à démarrer sans MCJ, et a peu près 1.73s avec MCJ. Sur un Quad Core i7 2.8Ghz, l'application démarre en bien moins qu'une seconde, ce qui rend la mesure particulièrement peu fiable, surtout lorsque l'on considère la performance ressentie.
Je suis très impatient de savoir ce qui va se passer sur des tablettes WoA... Enfin, on le saura à l'automne.
Désavantages du MCJ
Bien que cette fonctionnalité est très intéressante, elle enregistre un démarrage de l'application. Cela veut dire que dès que le code change, le MCJ va se mettre a JITter des méthodes qui ne sont potentiellement plus dans le chemin de démarrage.
Cela ne va pas déstabiliser l'application, mais elle va perdre progressivement les bénéfices du MCJ, et dans le pire des cas, dégrader le temps de démarrage de l'application. Mais c'est très peu probable.
Vous devez alors penser à recréer les profils de JIT juste avant de publier votre application, et ne pas s'appuyer sur des profils générés précédemment.