Medición del impacto de la extensión en el inicio

Centrarse en el rendimiento de la extensión en Visual Studio 2017

En función de los comentarios de los clientes, una de las áreas de enfoque de la versión de Visual Studio 2017 ha sido el rendimiento de la carga de la solución y el inicio. Como equipo de la plataforma de Visual Studio, hemos estado trabajando para mejorar el rendimiento de la carga de la solución y el inicio. En general, nuestras medidas sugieren extensiones instaladas también pueden tener un impacto considerable en esos escenarios.

Para ayudar a los usuarios a comprender este impacto, hemos agregado una nueva característica en Visual Studio para notificar a los usuarios de extensiones lentas. A veces, Visual Studio detecta una nueva extensión que ralentiza la carga o el inicio de la solución. Cuando se detecta una ralentización, los usuarios verán una notificación en el IDE que les apunta al nuevo cuadro de diálogo "Administrar rendimiento de Visual Studio". También se puede acceder a este cuadro de diálogo mediante el menú Ayuda para examinar las extensiones detectadas anteriormente.

manage Visual Studio performance

Este documento tiene como objetivo ayudar a los desarrolladores de extensiones a describir cómo se calcula el impacto de la extensión. En este documento también se describe cómo se puede analizar el impacto de la extensión localmente. El análisis local del impacto en la extensión determinará si una extensión puede mostrarse como una extensión que afecta al rendimiento.

Nota:

Este documento se centra en el impacto de las extensiones en el inicio y la carga de la solución. Las extensiones también afectan al rendimiento de Visual Studio cuando hacen que la interfaz de usuario deje de responder. Para obtener más información sobre este tema, consulte Cómo: Diagnosticar retrasos en la interfaz de usuario causados por extensiones.

Cómo pueden afectar las extensiones al inicio

Una de las formas más comunes de que las extensiones afecten al rendimiento de inicio es elegir cargar automáticamente en uno de los contextos de interfaz de usuario de inicio conocidos, como NoSolutionExists o ShellInitialized. Estos contextos de interfaz de usuario se activan durante el inicio. Los paquetes que incluyan el ProvideAutoLoad atributo en su definición con esos contextos se cargarán e inicializarán en ese momento.

Cuando medimos el impacto de una extensión, nos centramos principalmente en el tiempo invertido por esas extensiones que eligen cargar automáticamente en los contextos anteriores. Los tiempos medidos incluirían, entre otros, los siguientes:

  • Carga de ensamblados de extensión para paquetes sincrónicos
  • Tiempo invertido en el constructor de clase de paquete para paquetes sincrónicos
  • Tiempo invertido en el método Initialize (o SetSite) del paquete para paquetes sincrónicos
  • En el caso de los paquetes asincrónicos, las operaciones anteriores se ejecutan en subproceso en segundo plano. Por lo tanto, las operaciones se excluyen de la supervisión.
  • Tiempo invertido en cualquier trabajo asincrónico programado durante la inicialización del paquete para ejecutarse en el subproceso principal
  • Tiempo invertido en controladores de eventos, específicamente la activación de contexto inicializada del shell o el cambio de estado zombi del shell
  • A partir de Visual Studio 2017 Update 3, también empezaremos a supervisar el tiempo invertido en llamadas inactivas antes de inicializar el shell. Las operaciones largas en los controladores inactivos también provocan un IDE que no responde y contribuyen al tiempo de inicio percibido por el usuario.

Hemos agregado muchas características a partir de Visual Studio 2015. Estas características ayudan a eliminar la necesidad de que los paquetes se carguen automáticamente. Las características también posponen la necesidad de que los paquetes se carguen en casos más específicos. Estos casos incluyen ejemplos en los que los usuarios estarían más seguros de usar la extensión o reducir un impacto en la extensión al cargarse automáticamente.

Puede encontrar más detalles sobre estas características en los siguientes documentos:

Contextos de interfaz de usuario basados en reglas: un motor basado en reglas más completo creado en torno a contextos de interfaz de usuario permite crear contextos personalizados basados en tipos de proyecto, tipos y atributos. Los contextos personalizados se pueden usar para cargar un paquete durante escenarios más específicos. Estos escenarios específicos incluyen la presencia de un proyecto con una funcionalidad específica en lugar del inicio. Los contextos personalizados también permiten vincular la visibilidad de comandos a un contexto personalizado basado en componentes del proyecto u otros términos disponibles. Esta característica elimina la necesidad de cargar un paquete para registrar un controlador de consulta de estado de comandos.

Compatibilidad con paquetes asincrónicos: la nueva clase base AsyncPackage en Visual Studio 2015 permite cargar paquetes de Visual Studio en segundo plano de forma asincrónica si se solicitó la carga de paquetes mediante un atributo de carga automática o una consulta de servicio asincrónica. Esta carga en segundo plano permite que el IDE siga respondiendo. El IDE responde incluso mientras la extensión se inicializa en segundo plano y los escenarios críticos, como el inicio y la carga de la solución, no se verían afectados.

Servicios asincrónicos: con compatibilidad con paquetes asincrónicos, también se ha agregado compatibilidad para consultar servicios de forma asincrónica y poder registrar servicios asincrónicos. Lo más importante es que estamos trabajando en la conversión de servicios principales de Visual Studio para admitir consultas asincrónicas para que la mayoría del trabajo en una consulta asincrónica se produzca en subprocesos en segundo plano. SComponentModel (host MEF de Visual Studio) es uno de los principales servicios que ahora admiten consultas asincrónicas para permitir que las extensiones admitan la carga asincrónica por completo.

Reducción del impacto de las extensiones cargadas automáticamente

Si todavía es necesario cargar automáticamente un paquete al iniciarse, es importante minimizar el trabajo realizado durante la inicialización del paquete. Minimizar el trabajo de inicialización del paquete reduce las posibilidades de que la extensión afecte al inicio.

Algunos ejemplos que podrían hacer que la inicialización del paquete sea costosa son:

Uso de la carga del paquete sincrónico en lugar de la carga de paquetes asincrónica

Dado que los paquetes sincrónicos se cargan en el subproceso principal de forma predeterminada, recomendamos a los propietarios de extensiones que tengan paquetes cargados automáticamente para usar la clase base de paquete asincrónica, como se mencionó anteriormente. Cambiar un paquete cargado automáticamente para admitir la carga asincrónica también facilita la resolución de los otros problemas siguientes.

Solicitudes de E/S de archivo o red sincrónicas

Lo ideal es que se evite cualquier solicitud de E/S de red o archivo sincrónico en el subproceso principal. Su impacto dependerá del estado de la máquina y puede bloquearse durante largos períodos de tiempo en algunos casos.

El uso de la carga asincrónica de paquetes y las API de E/S asincrónicas debe asegurarse de que la inicialización del paquete no bloquee el subproceso principal en estos casos. Los usuarios también pueden seguir interactuando con Visual Studio mientras se producen solicitudes de E/S en segundo plano.

Inicialización temprana de servicios, componentes

Uno de los patrones comunes en la inicialización de paquetes es inicializar los servicios usados por o proporcionados por ese paquete en el paquete constructor o initialize método. Aunque esto garantiza que los servicios estén listos para usarse, también puede agregar costos innecesarios a la carga de paquetes si esos servicios no se usan inmediatamente. En su lugar, estos servicios deben inicializarse a petición para minimizar el trabajo realizado en la inicialización del paquete.

En el caso de los servicios globales proporcionados por un paquete, puede usar AddService métodos que toman una función para inicializar el servicio de forma diferida solo cuando un componente lo solicita. En el caso de los servicios usados en el paquete, puede usar Lazy<T> o AsyncLazy<T> para asegurarse de que los servicios se inicializan o consultan en primer uso.

Medición del impacto de las extensiones cargadas automáticamente mediante el registro de actividad

A partir de Visual Studio 2017 Update 3, el registro de actividad de Visual Studio ahora contendrá entradas para el impacto en el rendimiento de los paquetes durante el inicio y la carga de la solución. Para ver estas medidas, debe abrir Visual Studio con el modificador /log y abrir el archivo ActivityLog.xml .

En el registro de actividad, las entradas estarán en el origen "Administrar el rendimiento de Visual Studio" y tendrán un aspecto similar al del ejemplo siguiente:

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

En este ejemplo se muestra que un paquete con GUID "3cd7f5bf-6662-4ff0-ade8-97b5ff12f39c" pasó 2008 ms en el inicio de Visual Studio. Tenga en cuenta que Visual Studio considera el costo de nivel superior como el número principal al calcular el impacto de un paquete, ya que sería el ahorro que verían los usuarios cuando deshabilitan la extensión para ese paquete.

Medición del impacto de las extensiones cargadas automáticamente mediante PerfView

Aunque el análisis de código puede ayudar a identificar rutas de acceso de código que pueden ralentizar la inicialización de paquetes, también puede usar el seguimiento mediante aplicaciones como PerfView para comprender el impacto de una carga de paquetes en el inicio de Visual Studio.

PerfView es una herramienta de seguimiento para todo el sistema. Esta herramienta le ayudará a comprender las rutas de acceso activas en una aplicación debido al uso de CPU o al bloqueo de llamadas del sistema. A continuación se muestra un ejemplo rápido sobre el análisis de una extensión de ejemplo mediante PerfView.

Código de ejemplo:

Este ejemplo se basa en el código de ejemplo siguiente, que está diseñado para mostrar casos de algunas causas de retraso comunes:

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(); });
}

Grabación de un seguimiento con PerfView:

Una vez configurado el entorno de Visual Studio con la extensión instalada, puede registrar un seguimiento del inicio abriendo PerfView y abriendo el cuadro de diálogo Recopilar en el menú Recopilar.

perfview collect menu

Las opciones predeterminadas proporcionarán pilas de llamadas para el consumo de CPU, pero como también estamos interesados en el tiempo de bloqueo, también debe habilitar las pilas de tiempo de subproceso . Una vez que la configuración esté lista, puede hacer clic en Iniciar recopilación y abrir Visual Studio después de iniciar la grabación.

Antes de detener la recopilación, quiere asegurarse de que Visual Studio está completamente inicializado, la ventana principal está completamente visible y si la extensión tiene partes de interfaz de usuario que se muestran automáticamente, también son visibles. Cuando Visual Studio está completamente cargado y se inicializa la extensión, puede detener la grabación para analizar el seguimiento.

Análisis de un seguimiento con PerfView:

Una vez completada la grabación, PerfView abrirá automáticamente las opciones de seguimiento y expandirá.

Para los fines de este ejemplo, estamos interesados principalmente en la vista Pilas de tiempo de subproceso que puede encontrar en Grupo avanzado. Esta vista mostrará el tiempo total invertido en un subproceso por un método, incluido el tiempo de CPU y el tiempo bloqueado, como la E/S de disco o la espera de identificadores.

thread time stacks

Al abrir la vista Pilas de tiempo de subproceso, debe elegir el proceso de devenv para iniciar el análisis.

PerfView tiene instrucciones detalladas sobre cómo leer pilas de tiempo de subproceso en su propio menú ayuda para un análisis más detallado. Para los fines de este ejemplo, queremos filtrar esta vista aún más mediante la inclusión de pilas con el nombre del módulo de paquetes y el subproceso de inicio.

  1. Establezca GroupPats en texto vacío para quitar cualquier agrupación agregada de forma predeterminada.
  2. Establezca IncPats para incluir parte del nombre del ensamblado y subproceso de inicio además del filtro de proceso existente. En este caso, debe ser devenv; Subproceso de inicio; MakeVsSlowExtension.

Ahora la vista solo mostrará el costo asociado a los ensamblados relacionados con la extensión. En esta vista, cualquier vez que aparezca en la columna Inc (Costo inclusivo) del subproceso de inicio está relacionado con nuestra extensión filtrada y afectará al inicio.

En el ejemplo anterior, algunas pilas de llamadas interesantes serían:

  1. E/S mediante System.IO clase: aunque es posible que el costo inclusivo de estos fotogramas no sea demasiado caro en el seguimiento, son una posible causa de un problema, ya que la velocidad de E/S de archivo variará de la máquina a la máquina.

    system io frames

  2. Bloqueo de llamadas en espera de otro trabajo asincrónico: en este caso, el tiempo inclusivo representaría la hora a la que se bloquea el subproceso principal al completar el trabajo asincrónico.

    blocking call frames

Una de las otras vistas del seguimiento que será útil para determinar el impacto será las pilas de carga de imágenes. Puede aplicar los mismos filtros que se aplican a la vista Pilas de tiempo de subproceso y averiguar todos los ensamblados cargados debido al código ejecutado por el paquete cargado automáticamente.

Es importante minimizar el número de ensamblados cargados dentro de una rutina de inicialización de paquetes, ya que cada ensamblado adicional implicará una E/S de disco adicional que puede ralentizar considerablemente el inicio en máquinas más lentas.

Resumen

El inicio de Visual Studio ha sido una de las áreas en las que recibimos comentarios continuamente. Nuestro objetivo como se indicó anteriormente es que todos los usuarios tengan una experiencia de inicio coherente independientemente de los componentes y extensiones que hayan instalado. Nos gustaría trabajar con propietarios de extensiones para ayudarles a lograr ese objetivo. Las instrucciones anteriores deben ser útiles para comprender el impacto de las extensiones en el inicio y evitar la necesidad de cargarla automáticamente o cargarla de forma asincrónica para minimizar el impacto en la productividad del usuario.