Поделиться через


Измерение влияния расширения на запуск

Внимание к производительности расширений в Visual Studio 2017

На основе отзывов клиентов одна из основной области выпуска Visual Studio 2017 была запуском и производительностью загрузки решения. Как команда платформы Visual Studio, мы работали над улучшением производительности загрузки запуска и решения. Как правило, наши измерения предлагают установленные расширения также могут оказать значительное влияние на эти сценарии.

Чтобы помочь пользователям понять это влияние, мы добавили новую функцию в Visual Studio, чтобы уведомить пользователей о медленных расширениях. Иногда Visual Studio обнаруживает новое расширение, которое замедляет загрузку решения или запуск. При обнаружении замедления пользователи увидят уведомление в интегрированной среде разработки, указывая на новое диалоговое окно "Управление производительностью Visual Studio". Это диалоговое окно также можно получить в меню справки, чтобы просмотреть ранее обнаруженные расширения.

manage Visual Studio performance

Этот документ направлен на помощь разработчикам расширений, описывая способ вычисления влияния на расширение. В этом документе также описывается, как можно анализировать влияние расширения локально. Локальное анализ влияния на расширение определяет, может ли расширение отображаться как расширение, влияющее на производительность.

Примечание.

В этом документе основное внимание уделяется влиянию расширений на загрузку запуска и решения. Расширения также влияют на производительность Visual Studio, когда пользовательский интерфейс не отвечает. Дополнительные сведения см. в разделе "Практическое руководство. Диагностика задержек пользовательского интерфейса, вызванных расширениями".

Как расширения могут повлиять на запуск

Одним из наиболее распространенных способов повышения производительности запуска является автоматическая загрузка в одном из известных контекстов пользовательского интерфейса запуска, таких как NoSolutionExists или ShellInitialized. Эти контексты пользовательского интерфейса активируются во время запуска. Все пакеты, которые включают ProvideAutoLoad атрибут в их определение с этими контекстами, будут загружены и инициализированы в это время.

При измерении влияния расширения мы главным образом сосредоточимся на времени, затраченном этими расширениями, которые выбирают автоматическую загрузку в контекстах выше. Измеряемое время включает в себя, но не ограничивается следующими значениями:

  • Загрузка сборок расширения для синхронных пакетов
  • Время, затраченное в конструкторе класса пакета для синхронных пакетов
  • Время, затраченное на метод initialize (или SetSite) для синхронных пакетов
  • Для асинхронных пакетов описанные выше операции выполняются в фоновом потоке. Таким образом, операции исключаются из мониторинга.
  • Время, затраченное на любую асинхронную работу, запланированную во время инициализации пакета для выполнения в основном потоке
  • Время, затраченное на обработчики событий, в частности активация контекста оболочки или изменение состояния зомби оболочки
  • Начиная с Visual Studio 2017 с обновлением 3, мы также начнем время мониторинга, потраченное на неактивные вызовы до инициализации оболочки. Длительные операции в обработчиках простоя также приводят к неответственной интегрированной среде разработки и способствуют предполагаемому времени запуска пользователем.

Мы добавили множество функций, начиная с Visual Studio 2015. Эти функции помогают удалить необходимость автоматической загрузки пакетов. Функции также откладывают необходимость загрузки пакетов в более конкретные случаи. Эти случаи включают примеры, когда пользователи будут более уверены в использовании расширения или снизить влияние расширения при автоматической загрузке.

Дополнительные сведения об этих функциях см. в следующих документах:

Контексты пользовательского интерфейса на основе правил: более широкий механизм на основе правил, построенный вокруг контекстов пользовательского интерфейса, позволяет создавать настраиваемые контексты на основе типов проектов, вкусов и атрибутов. Пользовательские контексты можно использовать для загрузки пакета во время более конкретных сценариев. К этим конкретным сценариям относятся наличие проекта с определенной возможностью вместо запуска. Пользовательские контексты также позволяют связать видимость команд с пользовательским контекстом на основе компонентов проекта или других доступных терминов. Эта функция устраняет необходимость загрузки пакета для регистрации обработчика запроса состояния команды.

Поддержка асинхронного пакета: новый базовый класс AsyncPackage в Visual Studio 2015 позволяет пакетам Visual Studio загружаться в фоновом режиме асинхронно, если загрузка пакета запрашивается атрибутом автоматической загрузки или асинхронным запросом службы. Эта фоновая загрузка позволяет интегрированной среде разработки реагировать. Интегрированная среда разработки реагирует, даже если расширение инициализировано в фоновых и критически важных сценариях, таких как загрузка запуска и решения, не будет влиять.

Асинхронные службы: с поддержкой асинхронного пакета мы также добавили поддержку запросов к службам асинхронно и возможность регистрировать асинхронные службы. Более важно, что мы работаем над преобразованием основных служб Visual Studio для поддержки асинхронного запроса, чтобы большинство работ в асинхронном запросе произошло в фоновых потоках. SComponentModel (узел MEF Visual Studio) является одной из основных служб, которые теперь поддерживают асинхронный запрос, чтобы разрешить расширениям полностью поддерживать асинхронную загрузку.

Снижение влияния автоматически загруженных расширений

Если пакет по-прежнему должен быть автоматически загружен при запуске, важно свести к минимуму работу во время инициализации пакета. Минимизация работы инициализации пакета снижает вероятность влияния на запуск расширения.

Ниже приведены некоторые примеры, которые могут привести к инициализации пакета:

Использование синхронной загрузки пакета вместо асинхронной загрузки пакета

Так как синхронные пакеты загружаются в основной поток по умолчанию, мы рекомендуем владельцам расширений, имеющим автоматически загруженные пакеты, использовать асинхронный базовый класс пакета, как упоминание ранее. Изменение автоматического загруженного пакета для поддержки асинхронной загрузки также упрощает устранение других проблем, приведенных ниже.

Синхронные запросы операций ввода-вывода файлов и сети

В идеале любой синхронный файл или сетевой запрос ввода-вывода следует избегать в основном потоке. Их влияние будет зависеть от состояния компьютера и может блокироваться в течение длительного периода времени в некоторых случаях.

Использование асинхронной загрузки пакетов и асинхронных API-интерфейсов ввода-вывода должно убедиться, что инициализация пакета не блокирует основной поток в таких случаях. Пользователи также могут продолжать взаимодействовать с Visual Studio, пока запросы ввода-вывода выполняются в фоновом режиме.

Ранняя инициализация служб, компонентов

Одним из распространенных шаблонов инициализации пакетов является инициализация служб, используемых или предоставленных этим пакетом в пакете constructor или initialize методе. Хотя это гарантирует, что службы готовы к использованию, он также может добавить ненужные затраты на загрузку пакетов, если эти службы не используются немедленно. Вместо этого такие службы следует инициализировать по требованию, чтобы свести к минимуму работу, выполняемую в инициализации пакета.

Для глобальных служб, предоставляемых пакетом, можно использовать AddService методы, которые выполняют функцию, чтобы инициализировать службу только в том случае, если он запрашивается компонентом. Для служб, используемых в пакете, можно использовать Lazy<T> или AsyncLazy<T> , чтобы убедиться, что службы инициализированы или запрашиваются при первом использовании.

Измерение влияния автоматически загруженных расширений с помощью журнала действий

Начиная с Visual Studio 2017 с обновлением 3, журнал действий Visual Studio теперь будет содержать записи для повышения производительности пакетов во время загрузки запуска и решения. Чтобы увидеть эти измерения, необходимо открыть Visual Studio с параметром /log и открыть файл ActivityLog.xml .

В журнале действий записи будут находиться в разделе "Управление производительностью Visual Studio" и будут выглядеть следующим образом:

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

В этом примере показано, что пакет с GUID "3cd7f5bf-6662-4ff0-ade8-97b5ff12f39c" провел 2008 мс при запуске Visual Studio. Обратите внимание, что Visual Studio рассматривает стоимость верхнего уровня как основное число при вычислении влияния пакета, так как это будет экономия пользователей, когда они отключают расширение для этого пакета.

Измерение влияния автоматически загруженных расширений с помощью PerfView

Хотя анализ кода может помочь определить пути кода, которые могут замедлить инициализацию пакета, можно также использовать трассировку с помощью приложений, таких как PerfView , чтобы понять влияние загрузки пакета в запуске Visual Studio.

PerfView — это средство трассировки на уровне системы. Это средство поможет вам понять горячие пути в приложении из-за использования ЦП или блокировки системных вызовов. Ниже приведен краткий пример анализа примера расширения с помощью PerfView.

Пример кода:

Этот пример основан на приведенном ниже примере кода, который предназначен для отображения некоторых распространенных причин задержки:

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

Запись трассировки с помощью PerfView:

После настройки среды Visual Studio с установленным расширением можно записать трассировку запуска, открыв PerfView и открыв диалоговое окно "Сбор" в меню "Сбор".

perfview collect menu

Параметры по умолчанию предоставляют стеки вызовов для потребления ЦП, но так как мы заинтересованы в блокировке времени, также следует включить стеки времени потока. После подготовки параметров можно щелкнуть "Запустить коллекцию" , а затем открыть Visual Studio после начала записи.

Прежде чем остановить коллекцию, необходимо убедиться, что Visual Studio полностью инициализирована, главное окно полностью отображается и если расширение содержит элементы пользовательского интерфейса, которые автоматически отображаются, они также видны. Когда Visual Studio полностью загружен и инициализировано расширение, можно остановить запись для анализа трассировки.

Анализ трассировки с помощью PerfView:

После завершения записи PerfView автоматически откроет параметры трассировки и развертывания.

В этом примере мы в основном заинтересованы в представлении стека времени потока, которое можно найти в разделе "Расширенная группа". В этом представлении будет отображаться общее время, затраченное на поток методом, включая время ЦП и заблокированное время, например операций ввода-вывода диска или ожидание дескрипторов.

thread time stacks

При открытии представления стека времени потока следует выбрать процесс devenv , чтобы начать анализ.

PerfView содержит подробные инструкции по чтению стеков времени потока в собственном меню справки для более подробного анализа. В этом примере мы хотим отфильтровать это представление дальше, только включив стеки с именем модуля пакетов и потоком запуска.

  1. Задайте groupPats пустым текстом, чтобы удалить любую группирование, добавленную по умолчанию.
  2. Задайте IncPats , чтобы включить часть имени сборки и потока запуска в дополнение к существующему фильтру процессов. В этом случае он должен быть девенв; Запуск потока; MakeVsSlowExtension.

Теперь в представлении будут отображаться только затраты, связанные с сборками, связанными с расширением. В этом представлении любое время, указанное в столбце Inc (инклюзивной стоимости) потока запуска, связано с нашим фильтрованным расширением и будет влиять на запуск.

В примере выше приведены некоторые интересные стеки вызовов:

  1. Класс использования System.IO операций ввода-вывода: хотя инклюзивная стоимость этих кадров может быть не слишком дорогой в трассировке, они являются потенциальной причиной проблемы, так как скорость ввода-вывода файлов будет отличаться от компьютера до компьютера.

    system io frames

  2. Блокировка вызовов, ожидающих других асинхронных работ: в этом случае инклюзивное время будет представлять время блокировки основного потока при завершении асинхронной работы.

    blocking call frames

Одно из других представлений трассировки, которое будет полезно определить влияние, будет стеками загрузки изображений. Вы можете применить те же фильтры, что и в представлении стеками времени потока, и узнать все сборки, загруженные из-за кода, выполняемого автоматически загруженным пакетом.

Важно свести к минимуму количество загруженных сборок внутри подпрограммы инициализации пакета, так как каждая дополнительная сборка будет включать дополнительные операции ввода-вывода диска, которые могут значительно замедлить запуск на более медленных компьютерах.

Итоги

Запуск Visual Studio был одним из областей, о которых мы постоянно получаем отзывы. Наша цель, как указано ранее, заключается в том, чтобы все пользователи имели согласованный интерфейс запуска независимо от компонентов и расширений, которые они установили. Мы хотели бы работать с владельцами расширений, чтобы помочь им достичь этой цели. Приведенные выше рекомендации должны быть полезны для понимания расширений, влияющих на запуск, и либо избегая необходимости автозагрузить или загружать его асинхронно, чтобы свести к минимуму влияние на производительность пользователей.