Работа с большими решениями .NET 5 в Visual Studio 2019 16.8
С выпуском .NET 5 миграция решений из .NET Framework увеличилась. В частности, мы начали наблюдать перемещение очень крупных решений. Чтобы обеспечить максимальное удобство, мы работали над оптимизацией Visual Studio для обработки решений, содержащих большое количество проектов .NET 5 и .NET Core. Многие из этих оптимизаций были включены в версию 16.8, и в этом посте рассматриваются внесенные нами улучшения.
Запуск компилятора C# и VB вне процесса
Roslyn, компилятор C# и Visual Basic, парсит и анализирует все решение для поддержки служб, таких как IntelliSense, Go to Definition и диагностика ошибок. В результате Roslyn имеет тенденцию потреблять ресурсы, которые увеличиваются пропорционально размеру открытого решения, что может стать весьма значительным для больших решений. Команда Roslyn уже работала над минимизацией этого воздействия, активно кэшируя информацию на диске, которая не требуется немедленно, но даже при таком кэшировании нельзя избежать необходимости хранить данные в памяти.
Чтобы уменьшить влияние на более крупные решения Visual Studio, команда Roslyn приложила значительные усилия, чтобы вывести компилятор Roslyn из процесса Visual Studio в его собственный процесс. Запуск Roslyn в собственном процессе освобождает ресурсы в самой Visual Studio и дает компилятору Roslyn больше места для выполнения своей работы. Для больших решений это может сэкономить до трети памяти, потребляемой Visual Studio при открытии большого решения.
Оптимизация узла зависимостей
Каждый проект .NET 5 и .NET Core имеет узел в обозревателе решений с именем «Dependencies», который отображает все, от чего зависит проект: другие проекты, сборки, пакеты NuGet и т.д. В дополнение к отображению непосредственных зависимостей проекта, узел также показывает транзитивные зависимости проекта, т.е. все, от чего зависит каждая зависимость, и так далее. Для проекта любого разумного размера этот список транзитивных зависимостей может стать довольно большим.
К сожалению, первоначальная реализация узла «Dependencies» не была очень эффективной в том смысле, что она хранила информацию о переходных зависимостях в памяти. В нем содержалось гораздо больше информации, чем было необходимо, и большая часть данных была избыточной. Мы переписали код, чтобы сохранить только ту информацию, которая абсолютно необходима, и начали использовать существующую информацию о зависимостях, которая уже хранится в NuGet. Эта перезапись сэкономила до 10–15% памяти, потребляемой Visual Studio при открытии большого решения.
Уменьшение дублирования информации в MSBuild
После Roslyn одним из других основных потребителей ресурсов в процессе Visual Studio является MSBuild. Это связано с тем, что в качестве механизма сборки большая часть среды IDE основана на объектной модели MSBuild. Хотя мы сделали сами файлы проектов намного меньше в .NET 5 и .NET Core, существует значительное количество вспомогательных файлов проектов, которые импортируются в проекты через SDK. Оценка всех этих файлов необходима для понимания и построения проекта и может потреблять до трети памяти, нужной Visual Studio при открытии большого решения.
Хотя файлы проекта генерируют много данных, большая часть этих данных повторяется, и мы начали дедупликацию этих данных в памяти. Строки — один из наиболее распространенных побочных продуктов системы проектов, и они хранят такую информацию, как имена файлов, параметры и пути. В частности, пути могут быть довольно длинными и потреблять много памяти, если их слишком много или они дублируются слишком много раз. Обеспечено хранение только одной копии строки, что позволяет сэкономить до 5–10% памяти, потребляемой Visual Studio при открытии большого решения.
Уменьшение количества копий проекта, удерживаемых системой проектов
Одним из важных аспектов проектирования системы проектов .NET 5 и .NET Core является асинхронность. Часто системе проекта требуется выполнять работу в ответ на действие пользователя (например, добавление новой ссылки в проект). Вместо того, чтобы блокировать Visual Studio, пока она завершает свою работу, система проектов позволяет пользователю продолжать работу в среде IDE и выполнять работу в фоновом режиме.
Поскольку пользователь может продолжать вносить изменения в проект (например, добавлять еще одну ссылку), пока система проектов обрабатывает предыдущие изменения в фоновом режиме, система проекта должна сохранять моментальные снимки данных проекта, чтобы гарантировать, что последующее действие не конфликтует с более ранним. В результате система проектов может легко получить сразу несколько копий данных проекта в памяти. Если система проекта не заботится об управлении этими копиями, они могут храниться дольше, чем необходимо, или даже просочиться и сохраниться навсегда. Мы активно работали над сокращением количества копий, которые мы храним одновременно.
Улучшения загрузки большого решения
Проделав всю эту работу, мы значительно оптимизировали работу с большими решениями .NET 5 и .NET Core. Начиная с версии 16.8, во многих наших тестах мы наблюдали увеличение в 2,5 раза размера решения, которое мы можем открыть, прежде чем столкнуться с проблемами ресурсов. Мы также наблюдаем снижение до 25% сбоев, сообщаемых из-за исчерпания ресурсов.
Перечисленные выше улучшения — это только начало изменений, которые мы вносим для улучшения работы с большими решениями в Visual Studio. Производительность отдельных решений по-прежнему может варьироваться в зависимости от размера решения, типа проектов, загруженных расширений и т.д., И есть области, которые мы ищем для улучшения. Мы рекомендуем всем пользователям, у которых возникают проблемы с медленной загрузкой или сбоями при загрузке решений, обращаться к нам по адресу vssolutionload@microsoft.com, чтобы мы могли продолжать улучшать загрузку решений всех типов!