Уменьшение времени компиляции монолитной сборки .NET - инкрементная компиляция?

Мы используем .NET Framework 4.6 и в настоящее время имеем монолитную DLL, содержащую большую часть нашего приложения. Это около 700 тыс. Строк кода. Каждый раз, когда мы вносим изменения, перекомпиляция занимает больше минуты. Мы хотели бы ускорить это, если возможно.

Один из вариантов - разбить монолит на несколько одноранговых сборок, ни одна из которых не будет зависеть друг от друга (за исключением одной общей сборки для кода, совместно используемого проектами). Поэтому, если изменения ограничиваются одной сборкой, компилятору остается только перекомпилировать эту меньшую DLL. Кроме того, эти одноранговые сборки можно компилировать параллельно, что опять же потенциально сокращает время компиляции.

Другие предположили, что новые функции «инкрементальной сборки» .NET Core 2.x могут радикально увеличить время сборки монолита, подразумевая, что компилятор может перестраивать только те части монолита, которые изменились, игнорируя остальные.

Так ли это на самом деле или же не так работает новая функция инкрементальной сборки?

Или у кого-нибудь есть какие-либо другие советы, как лучше всего сократить время компиляции?

«Один из вариантов - разбить монолит на несколько равноправных сборок, поэтому, если изменения ограничиваются одной сборкой, компилятору нужно только перекомпилировать эту меньшую DLL». не будь в этом так уверен. Я работал над проектом, когда это было сделано настолько плохо, что одно изменение требовало перекомпиляции 5 проектов из-за зависимостей и занимало более 3 минут. И у него даже не было 50k LOC

Camilo Terevinto 02.10.2018 15:15

Я предполагаю, что ключевое слово здесь - «равноправный» - ни одна из сборок, на которые мы предлагаем разбить монолит, не будет зависеть друг от друга. Они будут листьями в дереве компиляции. Итак, я предполагаю, что изменение однорангового узла должно быть изолировано от других и, следовательно, требует только перекомпиляции самого однорангового узла. Это не так? Не могли бы вы поделиться своим опытом?

Mike Chamberlain 02.10.2018 18:19

Вот почему я сказал, что все пошло не так :) пока у них нет зависимостей, с вами должен все будет в порядке

Camilo Terevinto 02.10.2018 18:53

@MikeChamberlain: Как бы то ни было, то, что вы предлагаете, для меня звучит как полный стандарт. У нас есть несколько огромных приложений, которые состоят (иногда) из сотен отдельных сборок, без каких-либо проблем сборки, на которые намекает Камило.

500 - Internal Server Error 02.10.2018 21:36

Функция инкрементальной сборки или нет, если у монолита есть «компоненты», которые не связаны между собой, я считаю, что они должны быть перемещены в отдельные компоненты, просто проходящие через SOLID's S. Однако «сценарий отсутствия зависимости» это кажется маловероятным из того, что я понимаю, и даже в этом случае я все еще считаю, что принципы SOLID могут быть применены для повышения производительности, структурирования и обслуживания приложения.

adityap 04.10.2018 23:42

Звучит очевидно, но вы можете быстро улучшить ситуацию с помощью более совершенного оборудования. В противном случае разделение двоичного файла 700Kloc на несколько двоичных файлов также очень распространено (но, конечно, может потребовать много работы), не только для параллельной сборки, но также для масштабирования организации на каждом уровне процесса (план , дизайн, разработка, тесты, корабль и т. д. владение двоичными файлами). Всем должно быть проще.

Simon Mourier 05.10.2018 11:19
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
6
577
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Дополнительные сборки может помочь вам с вашей единственной монолитной DLL. Они были представлены в 2.0, но в основном они были сосредоточены на обнаружении изменений зависимостей - то, что Visual Studio уже делает для вас в вашем случае.

тем не мение, из Что нового в .NET Core 2.1:

Use of long-running SDK build servers, which are processes that span across individual dotnet build invocations. They eliminate the need to JIT-compile large blocks of code every time dotnet build is run.

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

Обновлять, я попытался точно проверить, что это может означать, но, к сожалению, у меня нет линейного проекта на 700 тыс. Для тестирования. Я использовал этот пример проекта MS и запустил 2 копии: одна была объединена в одну dll, а другая разделилась. Каждый тест, который я проводил, образец «монолитного» объединенного проекта выполнялся быстрее, но я предполагаю, что это неверные результаты, поскольку объединенный проект вряд ли можно считать «монолитным».

Одна интересная вещь, которую я обнаружил в этом образце «монолита»: внес ли я небольшое изменение в представление или радикальные изменения в проекте, я никогда не видел разницы между свежими временами компиляции. Это заставляет меня задаться вопросом, что именно этот новый сервер сборки кэширует с помощью JIT. Возможно, опять же, это просто потому, что мой «монолит» не велик.

В конце концов, я не могу найти доказательств того, что ваш действительно монолитный проект обеспечит более быструю сборку при внесении изменений в .NET Core 2.1. Я жестяная банка вижу доказательство того, что разделение его на пару функциональных проектов буду дает преимущества. Суть в том, что проекты, которые не изменились, не будут перекомпилированы.

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

Надеюсь это поможет!

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

Это можно сделать следующим образом (в Visual Studio 2015-2017):

  1. Откройте свое решение
  2. Для каждой сборки, которую вы хотите добавить, создайте отдельный проект в своем решении (щелкните правой кнопкой мыши в решении, чтобы открыть контекстное меню, затем выберите добавить -> новый проект ... в контекстном меню, затем выберите Visual C# -> Библиотека классов)
  3. Добавьте ссылку на свой основной проект (который является вашим монолитным приложением) для каждой созданной сборки (щелкните правой кнопкой мыши для ссылок, затем выберите добавить ссылку ... и просмотрите навигатор Проекты -> решение, чтобы выбрать его)
  4. Щелкните правой кнопкой мыши основной проект и выберите Зависимости сборки -> Зависимости проекта .... В диалоговом окне убедитесь, что ваш основной проект выбран в раскрывающемся списке. Отметьте каждую добавленную вами сборку. Теперь ваш основной проект зависит от сборок и будет компилироваться в правильном порядке.
  5. Переместите классы из основного проекта в проекты сборки (вырежьте и вставьте их). Если это создает какие-либо дополнительные зависимости, откройте Зависимости сборки -> Зависимости проекта ..., на этот раз в проекте, который зависит от разных сборок, и отметьте сборки, от которых он зависит.
  6. Щелкните решение правой кнопкой мыши и выберите восстановить решение.. Если вы все сделали правильно, компиляция должна завершиться успешно. В противном случае исправьте все возникающие ошибки (например, из-за отсутствия ссылок). Проверьте, не пропустили ли вы какие-либо инструкции using для импорта пространств имен.

С этого момента вам нужно создавать сборку только в том случае, если она изменилась (щелкните правой кнопкой мыши проект и выберите «построить»). Если есть какие-либо зависимости, компилятор автоматически компилирует его в правильном порядке сборки.

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

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

Вкладка Build Order показывает, в каком порядке они должны запускаться, вкладка Dependencies показывает для каждой сборки, от каких других сборок она зависит. Таким образом, вы можете узнать, есть ли какие-либо «ветки», которые могут быть скомпилированы параллельно с другими «ветвями» (под «ветвью» я имею в виду не ветки, контролируемые источником, а ветки вашего решения: подумайте о дереве, в котором основной проект является корневым - каждая ветвь состоит из последовательности сборок вашего решения, которые могут компилироваться параллельно).

Я согласен с его прекращением. Но есть много способов разбить решение. Я думаю, вам следует делать это поэтапно.

Фаза 1 - Самый быстрый способ разбить это сейчас.

Наиболее распространенные способы получить самые быстрые результаты:

  1. Все модели - создайте проект библиотеки моделей и переместите в него все файлы моделей. Пусть ваш монолит будет ссылаться на проект модели. Ни в коем случае не меняйте файлы или пространства имен. Просто вырезайте и вставьте из одного проекта в другой. Если какие-либо классы модели были частными, сделайте их внутренними (найдите и замените в проекте) и добавьте атрибут InternalsVisibleTo в сборку вашего монолита. Расчетное время перемещения всех классов моделей: 20 минут.

  2. Все интерфейсы - создайте проект библиотеки интерфейсов и переместите в него все файлы классов интерфейса. Пусть ваш монолит будет ссылаться на проект модели. Ни в коем случае не меняйте файлы или пространства имен. Просто вырезайте и вставьте из одного проекта в другой. Если какие-либо классы интерфейса были частными, сделайте их внутренними (найдите и замените в проекте) и добавьте атрибут InternalsVisibleTo в сборку вашего монолита. Расчетное время для перемещения всех классов интерфейса: 20 минут.

  3. Сгенерированный код - создайте проект GeneratedCode. Переместите в этот проект все классы, которые на 100% являются сгенерированным кодом. Конечно, предполагается, что, поскольку у вас есть 700 тыс. Строк кода, многие классы полностью сгенерированы. Если у вас нет сгенерированного кода, пропустите этот. Расчетное время для перемещения всего сгенерированного кода: 1 час.

  4. Методы расширения - создайте проект расширения и переместите в этот проект все классы методов расширения. Расчетное время для перемещения всех классов моделей: 30 минут.

  5. FixedLogic - Создайте проект с фиксированной логикой. Найдите все ваши редко используемые классы - в проекте его размера, вероятно, есть сотни файлов классов, которые не менялись годами. Вы снова и снова перекомпилируете эти классы без всякой причины. Вы должны выяснить, какие именно. Надеюсь, вы легко увидите это в системе управления версиями. Расчетное время: 2 часа на выяснение запроса в системе управления версиями. 10 минут, чтобы переместить эти файлы в новый проект.

  6. Оставайтесь на уровне 1 dll для вашего выпуска. Теперь я предполагаю, что у вас есть процесс сборки, развертывания и установки. Если вы этого не сделаете, пропустите этот шаг. Если вы это сделаете, и это будет сложно, вы можете нет захотеть добавить библиотеки DLL к вашим существующим процессам, потому что это добавит дополнительное время для этих задач. Поэтому убедитесь, что все файлы компилируются таким образом, чтобы по-прежнему получилась 1 dll. См. Эту статью: https://docs.microsoft.com/en-us/dotnet/framework/app-domains/how-to-build-a-multifile-assembly. Расчетное время на реализацию статьи: 4 часа.

Основная цель: после первой сборки последующие сборки создают только проекты с изменениями, что значительно сокращает время сборки.

Фаза 2 - лучший, но долгосрочный путь.

Не торопитесь, чтобы разбить свой код так, чтобы это было разумно. В любом случае делайте это только тогда, когда вы касаетесь кода.

  1. Проанализируйте свой проект и составьте список областей или доменов, которые охватывает ваш код. Это простой список в текстовом файле. У вас, вероятно, много доменов.

  2. Назначьте файлы классов этим доменам, но также отметьте файлы классов как Model, Interface, Logic.

  3. В следующий раз, когда вы коснетесь файла в домене, создайте три проекта: Domain.Models, Domain.Interfaces и Domain.Logic. Переместите код в правильный проект.

Основная цель - разделение проблем

Другие вопросы по теме