Mesure de l’impact de l’extension au démarrage

Concentrez-vous sur les performances des extensions dans Visual Studio 2017

En fonction des commentaires des clients, l’un des domaines de focus de la version de Visual Studio 2017 a été le démarrage et les performances de charge de la solution. En tant qu’équipe de plateforme Visual Studio, nous avons travaillé sur l’amélioration des performances de démarrage et de charge de solution. En général, nos mesures suggèrent que les extensions installées peuvent également avoir un impact considérable sur ces scénarios.

Pour aider les utilisateurs à comprendre cet impact, nous avons ajouté une nouvelle fonctionnalité dans Visual Studio pour informer les utilisateurs des extensions lentes. Parfois, Visual Studio détecte une nouvelle extension qui ralentit la charge de solution ou le démarrage. Lorsqu’un ralentissement est détecté, les utilisateurs voient une notification dans l’IDE qui les pointe vers la boîte de dialogue « Gérer les performances de Visual Studio ». Cette boîte de dialogue est également accessible par le menu Aide pour parcourir les extensions détectées précédemment.

manage Visual Studio performance

Ce document vise à aider les développeurs d’extensions en décrivant comment l’impact de l’extension est calculé. Ce document décrit également comment l’impact de l’extension peut être analysé localement. L’analyse locale de l’impact de l’extension détermine si une extension peut être affichée comme une extension ayant un impact sur les performances.

Remarque

Ce document se concentre sur l’impact des extensions sur le démarrage et la charge de la solution. Les extensions ont également un impact sur les performances de Visual Studio lorsqu’elles provoquent l’absence de réponse de l’interface utilisateur. Pour plus d’informations sur cette rubrique, consultez Guide pratique pour diagnostiquer les retards de l’interface utilisateur causés par les extensions.

Comment les extensions peuvent avoir un impact sur le démarrage

L’une des façons les plus courantes pour les extensions d’impacter les performances de démarrage consiste à choisir de charger automatiquement dans l’un des contextes d’interface utilisateur de démarrage connus tels que NoSolutionExists ou ShellInitialized. Ces contextes d’interface utilisateur sont activés au démarrage. Tous les packages qui incluent l’attribut ProvideAutoLoad dans leur définition avec ces contextes seront chargés et initialisés à ce moment-là.

Lorsque nous mesurons l’impact d’une extension, nous nous concentrons principalement sur le temps passé par ces extensions qui choisissent de charger automatiquement dans les contextes ci-dessus. Les heures mesurées incluent, mais ne sont pas limitées aux éléments suivants :

  • Chargement d’assemblys d’extension pour les packages synchrones
  • Temps passé dans le constructeur de classe de package pour les packages synchrones
  • Temps passé dans la méthode Initialize (ou SetSite) du package pour les packages synchrones
  • Pour les packages asynchrones, les opérations ci-dessus s’exécutent sur le thread d’arrière-plan. Par conséquent, les opérations sont exclues de la surveillance.
  • Temps passé dans tout travail asynchrone planifié pendant l’initialisation du package à exécuter sur le thread principal
  • Temps passé dans les gestionnaires d’événements, en particulier l’activation du contexte initialisé par l’interpréteur de commandes ou le changement d’état zombie de l’interpréteur de commandes
  • À partir de Visual Studio 2017 Update 3, nous allons également commencer à surveiller le temps consacré aux appels inactifs avant l’initialisation de l’interpréteur de commandes. Les opérations longues dans les gestionnaires inactifs entraînent également une absence de réponse de l’IDE et contribuent au temps de démarrage perçu par l’utilisateur.

Nous avons ajouté de nombreuses fonctionnalités à partir de Visual Studio 2015. Ces fonctionnalités permettent de supprimer la nécessité de charger automatiquement les packages. Les fonctionnalités reportent également la nécessité de charger des packages vers des cas plus spécifiques. Ces cas incluent des exemples où les utilisateurs seraient plus sûrs d’utiliser l’extension ou de réduire l’impact d’une extension lors du chargement automatiquement.

Vous trouverez plus d’informations sur ces fonctionnalités dans les documents suivants :

Contextes d’interface utilisateur basés sur des règles : un moteur basé sur des règles plus riche basé sur des contextes d’interface utilisateur vous permet de créer des contextes personnalisés basés sur des types de projet, des saveurs et des attributs. Les contextes personnalisés peuvent être utilisés pour charger un package dans des scénarios plus spécifiques. Ces scénarios spécifiques incluent la présence d’un projet avec une fonctionnalité spécifique au lieu du démarrage. Les contextes personnalisés permettent également d’attacher la visibilité des commandes à un contexte personnalisé en fonction des composants du projet ou d’autres termes disponibles. Cette fonctionnalité élimine la nécessité de charger un package pour inscrire un gestionnaire de requêtes d’état de commande.

Prise en charge des packages asynchrones : la nouvelle classe de base AsyncPackage dans Visual Studio 2015 permet aux packages Visual Studio d’être chargés en arrière-plan de manière asynchrone si la charge du package a été demandée par un attribut de chargement automatique ou une requête de service asynchrone. Ce chargement en arrière-plan permet à l’IDE de rester réactif. L’IDE est réactif même si l’extension est initialisée en arrière-plan et dans des scénarios critiques comme le démarrage et la charge de la solution ne seraient pas affectés.

Services asynchrones : avec la prise en charge des packages asynchrones, nous avons également ajouté la prise en charge de l’interrogation des services de manière asynchrone et la possibilité d’inscrire des services asynchrones. Plus important encore, nous travaillons à convertir les services Visual Studio principaux pour prendre en charge la requête asynchrone afin que la majorité du travail dans une requête asynchrone se produise dans des threads d’arrière-plan. SComponentModel (hôte VISUAL Studio MEF) est l’un des principaux services qui prennent désormais en charge la requête asynchrone pour permettre aux extensions de prendre en charge complètement le chargement asynchrone.

Réduction de l’impact des extensions chargées automatiquement

Si un package doit toujours être chargé automatiquement au démarrage, il est important de réduire le travail effectué lors de l’initialisation du package. La réduction du travail d’initialisation du package réduit les chances que l’extension affecte le démarrage.

Voici quelques exemples qui peuvent entraîner un initialisation de package coûteux :

Utilisation de la charge de package synchrone au lieu de la charge de package asynchrone

Étant donné que les packages synchrones sont chargés sur le thread principal par défaut, nous encourageons les propriétaires d’extensions qui ont des packages chargés automatiquement à utiliser la classe de base de package asynchrone, comme mentionné précédemment. La modification d’un package chargé automatiquement pour prendre en charge le chargement asynchrone facilite également la résolution des autres problèmes ci-dessous.

Demandes d’E/S de fichier/réseau synchrones

Dans l’idéal, toute requête d’E/S synchrone ou d’E/S réseau doit être évitée dans le thread principal. Leur impact dépend de l’état de l’ordinateur et peut être bloqué pendant de longues périodes dans certains cas.

L’utilisation d’API asynchrones de chargement de package et d’E/S asynchrones doit s’assurer que l’initialisation du package ne bloque pas le thread principal dans de tels cas. Les utilisateurs peuvent également continuer à interagir avec Visual Studio alors que les requêtes d’E/S se produisent en arrière-plan.

Initialisation anticipée des services, composants

L’un des modèles courants dans l’initialisation du package consiste à initialiser les services utilisés ou fournis par ce package dans le package constructor ou initialize la méthode. Bien que cela garantit que les services sont prêts à être utilisés, il peut également ajouter un coût inutile au chargement de package si ces services ne sont pas utilisés immédiatement. Au lieu de cela, ces services doivent être initialisés à la demande pour réduire le travail effectué dans l’initialisation du package.

Pour les services globaux fournis par un package, vous pouvez utiliser AddService des méthodes qui prennent une fonction pour initialiser de façon différée le service uniquement lorsqu’il est demandé par un composant. Pour les services utilisés dans le package, vous pouvez utiliser Lazy<T> ou AsyncLazy<T> pour vous assurer que les services sont initialisés/interrogés lors de la première utilisation.

Mesure de l’impact des extensions chargées automatiquement à l’aide du journal d’activité

À compter de Visual Studio 2017 Update 3, le journal d’activité Visual Studio contient désormais des entrées pour l’impact sur les performances des packages pendant le démarrage et la charge de la solution. Pour voir ces mesures, vous devez ouvrir Visual Studio avec /log switch et ouvrir le fichier ActivityLog.xml .

Dans le journal d’activité, les entrées sont sous la source « Gérer les performances de Visual Studio » et ressemblent à l’exemple suivant :

Component: 3cd7f5bf-6662-4ff0-ade8-97b5ff12f39c, Inclusive Cost: 2008.9381, Exclusive Cost: 2008.9381, Top Level Inclusive Cost: 2008.9381

Cet exemple montre qu’un package avec GUID « 3cd7f5bf-6662-4ff0-ade8-97b5ff12f39c » a passé 2008 ms au démarrage de Visual Studio. Notez que Visual Studio considère le coût de niveau supérieur comme le nombre principal lors du calcul de l’impact d’un package, car ce sont les utilisateurs qui voient les économies lorsqu’ils désactivent l’extension pour ce package.

Mesure de l’impact des extensions chargées automatiquement à l’aide de PerfView

Bien que l’analyse du code puisse aider à identifier les chemins de code qui peuvent ralentir l’initialisation du package, vous pouvez également utiliser le suivi à l’aide d’applications telles que PerfView pour comprendre l’impact d’une charge de package au démarrage de Visual Studio.

PerfView est un outil de suivi à l’échelle du système. Cet outil vous aidera à comprendre les chemins d’accès chauds d’une application en raison de l’utilisation du processeur ou du blocage des appels système. Voici un exemple rapide d’analyse d’un exemple d’extension à l’aide de PerfView.

Exemple de code :

Cet exemple est basé sur l’exemple de code ci-dessous, qui est conçu pour montrer les causes courantes des retards :

protected override void Initialize()
{
    // Initialize a class from another assembly as an example
    MakeVsSlowServiceImpl service = new MakeVsSlowServiceImpl();

    // Costly work in main thread involving file IO
    string systemPath = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
    foreach (string file in Directory.GetFiles(systemPath))
    {
        DateTime creationDate = File.GetCreationTime(file);
    }

    // Costly work after shell is initialized. This callback executes on main thread
    KnownUIContexts.ShellInitializedContext.WhenActivated(() =>
    {
        DoMoreWork();
    });

    // Start async work on background thread
    DoAsyncWork().Forget();
}

private async Task DoAsyncWork()
{
    // Switch to background thread to do expensive work
    await TaskScheduler.Default;
    System.Threading.Thread.Sleep(500);
}

private void DoMoreWork()
{
    // Costly work
    System.Threading.Thread.Sleep(500);
    // Blocking call to an asynchronous work.
    ThreadHelper.JoinableTaskFactory.Run(async () => { await DoAsyncWork(); });
}

Enregistrement d’une trace avec PerfView :

Une fois que vous avez configuré votre environnement Visual Studio avec votre extension installée, vous pouvez enregistrer une trace de démarrage en ouvrant PerfView et en ouvrant la boîte de dialogue Collecter à partir du menu Collecter.

perfview collect menu

Les options par défaut fournissent des piles d’appels pour la consommation du processeur, mais étant donné que nous sommes intéressés par le blocage du temps, vous devez également activer les piles de temps de thread. Une fois les paramètres prêts, vous pouvez cliquer sur Démarrer la collection , puis ouvrir Visual Studio après le démarrage de l’enregistrement.

Avant d’arrêter la collection, vous souhaitez vous assurer que Visual Studio est entièrement initialisé, la fenêtre principale est entièrement visible et si votre extension comporte des éléments d’interface utilisateur qui s’affichent automatiquement, ils sont également visibles. Lorsque Visual Studio est entièrement chargé et que votre extension est initialisée, vous pouvez arrêter l’enregistrement pour analyser la trace.

Analyse d’une trace avec PerfView :

Une fois l’enregistrement terminé, PerfView ouvre automatiquement la trace et développe les options.

Pour les besoins de cet exemple, nous sommes principalement intéressés par la vue Piles de temps de thread que vous trouverez sous Groupe avancé. Cette vue affiche le temps total passé sur un thread par une méthode, y compris le temps processeur et le temps bloqué, tels que les E/S de disque ou en attente de handles.

thread time stacks

Lors de l’ouverture de la vue Piles de temps thread, vous devez choisir le processus devenv pour démarrer l’analyse.

PerfView propose des instructions détaillées sur la lecture des piles de temps de thread sous son propre menu d’aide pour une analyse plus détaillée. Dans le cadre de cet exemple, nous souhaitons filtrer cette vue plus loin en incluant uniquement les piles avec le nom de notre module de packages et le thread de démarrage.

  1. Définissez GroupPats sur texte vide pour supprimer tout regroupement ajouté par défaut.
  2. Définissez IncPats pour inclure une partie de votre nom d’assembly et du thread de démarrage en plus du filtre de processus existant. Dans ce cas, il doit être dévenv ; Thread de démarrage ; MakeVsSlowExtension.

À présent, la vue affiche uniquement les coûts associés aux assemblys associés à l’extension. Dans cette vue, toute fois répertoriée sous la colonne Inc (coût inclusif) du thread de démarrage est liée à notre extension filtrée et aura un impact sur le démarrage.

Pour l’exemple ci-dessus, certaines piles d’appels intéressantes seraient les suivantes :

  1. E/S utilisant System.IO la classe : bien que le coût inclusif de ces images ne soit pas trop coûteux dans la trace, il s’agit d’une cause potentielle d’un problème, car la vitesse d’E/S du fichier varie de l’ordinateur à l’ordinateur.

    system io frames

  2. Blocage des appels en attente sur d’autres travaux asynchrones : dans ce cas, le temps inclusif représente l’heure à laquelle le thread principal est bloqué lors de l’achèvement du travail asynchrone.

    blocking call frames

L’une des autres vues de la trace qui sera utile pour déterminer l’impact sera les piles de chargement d’images. Vous pouvez appliquer les mêmes filtres que ceux appliqués à la vue Piles de temps de thread et rechercher tous les assemblys chargés en raison du code exécuté par votre package chargé automatiquement.

Il est important de réduire le nombre d’assemblys chargés à l’intérieur d’une routine d’initialisation de package, car chaque assembly supplémentaire implique des E/S de disque supplémentaires, ce qui peut ralentir considérablement le démarrage sur des machines plus lentes.

Résumé

Le démarrage de Visual Studio a été l’un des domaines sur lesquels nous recevons continuellement des commentaires. Notre objectif, comme indiqué précédemment, est que tous les utilisateurs aient une expérience de démarrage cohérente, quels que soient les composants et les extensions qu’ils ont installés. Nous aimerions travailler avec les propriétaires d’extensions pour les aider à atteindre cet objectif. Les conseils ci-dessus doivent être utiles pour comprendre un impact sur les extensions au démarrage et éviter la nécessité de charger automatiquement ou de le charger de manière asynchrone pour réduire l’impact sur la productivité des utilisateurs.