[Перевод] Ускорение Maven сборки

4f2f5d1945287b06359ed14811356c56

Узнайте, как сделать сборки Maven более быстрыми и эффективными

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

Я считаю, что скорость должна быть ниже в ​​порядке приоритета. Тем не менее, это также один из самых лимитирующих факторов на ваш цикл выпуска: если ваша сборка занимает T, вы не можете отпустить быстрее,  чем каждый из T

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

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

Исходный уровень

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

Я выбрал примеры кода Hazelcast,  потому что он обеспечивает достаточно большую многомодульную кодовую базу с множеством подмодулей;  точный коммит — 448febd.

Порядок действий следующий:

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

  • Я выполняю mvn clean перед каждым запуском, чтобы начать с пустого target репозитория

  • Все зависимости и плагины уже загружены

  • Я сообщаю время, которое Maven отображает в журнале консоли:

    [INFO] -------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] -------------------------------------------------------
    [INFO] Total time:  22.456 s (Wall Clock)
    [INFO] Finished at: 2021-09-24T23:20:41+02:00
    [INFO] -------------------------------------------------------

Начнем с нашего исходного уровня, выполнив команду:  mvn test. Результат:

  • 02:00 мин.

  • 01:57 мин.

  • 01:58 мин.

  • 01:56 мин.

  • 01:58 мин.

Использование всех процессоров

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

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

Это очень хорошо работает на нашей кодовой базе.

Мы собираемся использовать столько потоков, сколько доступно ядер. Соответствующая команда Maven: mvn test -T 1C.

При запуске этой команды вы должны увидеть в консоли следующее сообщение:

Using the MultiThreadedBuilder implementation with a thread count of X
  • 51.487 s (Wall Clock)

  • 40.322 s (Wall Clock)

  • 52.468 s (Wall Clock)

  • 41.862 s (Wall Clock)

  • 41.699 s (Wall Clock)

Цифры намного лучше, но с большей дисперсией.

Параллельное выполнение теста

Распараллеливание — отличный метод. Мы можем сделать то же самое в отношении выполнения тестов. По умолчанию плагин Maven Surefire запускает тесты последовательно, но можно настроить его для параллельного запуска тестов. Пожалуйста, обратитесь к документации за полным набором опций.

Этот подход отлично подходит, если у вас есть большое количество юнитов в каждом модуле. Обратите внимание, что ваши тесты должны быть независимыми друг от друга.

Мы вручную зададим количество потоков:

mvn test -Dparallel=all -DperCoreThreadCount=false -DthreadCount=16 #1 #2
  1. Настройте Surefire для параллельного запуска классов и методов

  2. Ручное изменение количества потоков до 16

Запустим и получим:

  • 02:04 мин.

  • 02:03 мин.

  • 01:46 мин

  • 01:52 мин

  • 01:53 мин.

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

Офлайн

Maven будет проверять, есть ли у SNAPSHOT зависимости новая «версия» при каждом запуске. Это означает дополнительные сетевые запросы. Мы можем предотвратить эту проверку с помощью опции --offline.

Хотя вам следует избегать SNAPSHOT зависимостей, иногда это неизбежно, особенно во время разработки.

Команда mvn test -o,  -o является сокращением для --offline.

  • 01:46 мин

  • 01:46 мин

  • 01:47 мин

  • 01:55 мин.

  • 01:44 мин

Кодовая база имеет большое количество SNAPSHOT зависимостей; следовательно, автономный режим значительно ускоряет сборку.

Параметры JVM

Сам Maven — это приложение на основе Java. Это означает, что каждый запуск запускает новую JVM. JVM сначала интерпретирует байт-код,  а затем анализирует рабочий код и соответственно компилирует байт-код в нативный код: это обеспечивает максимальную производительность, но только после длительного времени сборки. Это отлично подходит для длительно выполняющихся процессов, а не для приложений командной строки.

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

Мы можем настроить Maven, чтобы отказаться от него, настроив соответствующие параметры JVM. Доступно несколько способов настройки JVM. Самый простой способ — создать специальный jvm.config файл конфигурации во вложенной папке .mvn проекта.

-XX: -TieredCompilation -XX: TieredStopAtLevel = 1

Давайте теперь просто запустим mvn test:

  • 01:44 мин

  • 01:44 мин

  • 01:53 мин.

  • 01:53 мин.

  • 01:55 мин.

Maven Daemon

Демон Maven является недавним дополнением к экосистеме Maven. Он инспирирован демоном Gradle :

Gradle работает на виртуальной машине Java (JVM) и использует несколько вспомогательных библиотек, для которых требуется значительное время инициализации. 

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

Команда Gradle осознала, что инструмент командной строки — не лучшее применение JVM. Чтобы решить эту проблему, нужно постоянно поддерживать фоновый процесс JVM, используя демон. Он работает как сервер, а интерфейс командной строки играет роль клиента.

В качестве дополнительного преимущества этот длительный процесс загружает классы только один раз (если они не менялись между запусками).

После установки программного обеспечения вы можете запустить демон с помощью mvnd команды вместо стандартной mvn. Вот результаты выполнения команды:  mvnd test:

  • 33.124 s (Wall Clock)

  • 33.114 s (Wall Clock)

  • 34.440 s (Wall Clock)

  • 32.025 s (Wall Clock)

  • 29.364 s (Wall Clock)

Обратите внимание, что по умолчанию демон использует несколько потоков с расширением number of cores - 1.

Объединение методов

Мы видели несколько способов ускорить сборку. Что, если бы мы использовали их вместе?

Давайте сначала попробуем использовать все методы, которые мы видели до сих пор, в одном и том же прогоне:

mvnd test -Dparallel=all -DperCoreThreadCount=false -DthreadCount=16 -o #1 #2 #3 #4
  1. Используем демон Maven

  2. Запускаем тесты параллельно

  3. Не обновляем SNAPSHOT зависимости

  4. Настраиваем параметры JVM, как указано выше, через jvm.config файл — нет необходимости устанавливать какие-либо параметры

Команда возвращает следующие результаты:

  • 27.061 s (Wall Clock)

  • 24.457 s (Wall Clock)

  • 24.853 s (Wall Clock)

  • 25.772 s (Wall Clock)

Давайте вспомним, что демон Maven - это длительный процесс. По этой причине разумно позволить JVM проанализировать и скомпилировать байт-код в нативный код. Таким образом, мы можем удалить jvm.config файл и повторно запустить указанную выше команду. Результаты:

  • 23.840 s (Wall Clock)

  • 26.589 s (Wall Clock)

  • 22.283 s (Wall Clock)

  • 23.788 s (Wall Clock)

  • 22.456 s (Wall Clock)

Теперь мы можем отобразить сводные результаты:

Baseline

Parallel Build

Parallel Tests

Offline

Jvm Params

Daemon

Daemon + Offline + Parallel Tests + Parameters

Daemon + Offline + Parallel Tests

#1 (s)

120.00

51.00

128.00

106.00

104.00

33.12

27.06

23.84

#2 (s)

117.00

40.00

123.00

106.00

104.00

33.11

24.46

26.59

#3 (s)

118.00

52.00

106.00

107.00

113.00

34.44

24.85

22.28

#4 (s)

116.00

42.00

112.00

115.00

113.00

32.03

25.77

23.79

#5 (s)

118.00

42.00

113.00

104.00

115.00

29.36

22.46

Average (s)

*117.80*

45.40

116.40

107.60

109.80

32.41

25.54

23.79

Deviation

1.76

25.44

63.44

14.64

22.96

2.91

1.00

2.38

Gain from baseline (s)

-

72.40

1.40

10.20

8.00

85.39

92.26

94.01

% gain

-

61.46%

1.19%

8.66%

6.79%

72.48%

78.32%

79.80%

Заключение

В этом посте мы увидели несколько способов ускорить сборку Maven. Вот их краткое изложение:

  • Демон Maven: надежная и безопасная отправная точка

  • Распараллеливание сборки: когда сборка содержит несколько модулей, независимых друг от друга.

  • Распараллеливание тестов: когда проект содержит несколько тестов.

  • Автономность: когда проект содержит SNAPSHOTзависимости и вам не нужно их обновлять.

  • Параметры JVM: когда вы хотите сделать все возможное

Я бы посоветовал каждому пользователю начать использовать демон Maven и продолжить оптимизацию при необходимости и в зависимости от вашего проекта.

В следующем посте мы сосредоточимся на ускорении ваших сборок Maven в контейнере.

Чтобы продвинуться дальше, прочтите:

© Habrahabr.ru