Как определить максимальное число для передачи в качестве опции -j?

Вопрос или проблема

Я хочу компилировать как можно быстрее. Представьте себе. И хотел бы автоматизировать выбор числа, следующего за опцией -j. Как я могу программно выбрать это значение, например, в Shell-скрипте?

Является ли вывод nproc эквивалентным количеству потоков, которыми я могу воспользоваться для компиляции?

make -j1
make -j16

nproc выдает количество доступных текущему процессу ядер/потоков процессора, например, 8 на четырехъядерном процессоре с поддержкой SMT при отсутствии других ограничений (как, например, cgroups и т. д.).

Количество задач, которые вы можете запускать параллельно с make с использованием опции -j, зависит от ряда факторов:

  • количества доступной памяти
  • количества памяти, используемой каждой задачей make
  • степени, до которой задачи make зависят от ввода/вывода или ЦП

make -j$(nproc) — неплохое место для начала, но обычно вы можете использовать более высокие значения, пока не исчерпаете доступную память и не начнете перегружаться.

Для действительно быстрого компилирования, если у вас достаточно памяти, я рекомендую использовать tmpfs, таким образом большинство задач будет зависеть от ЦП, и make -j$(nproc) будет работать как можно быстрее.

Сейчас число систем с большим количеством процессоров становится все более распространенным, и, соответственно, объем памяти, доступный на одно ядро ЦП, обычно ниже, чем раньше, и многие сборки теперь требуют большого количества памяти, уравнение меняется. Было некоторое обсуждение этих вопросов в списке рассылки разработчиков Debian, начиная с этого сообщения об ошибке с просьбой добавить “новую опцию для уменьшения выделенных процессоров системной памятью” в nproc и завершение в Python-скрипте, который поддерживает указание объема памяти, необходимого для каждого ядра. Это позволит обеспечить лучшее значение для -j, если вы знаете, сколько памяти требуется.

Самый простой способ — использовать nproc следующим образом:

make -j$(nproc)

Команда nproc вернет количество ядер на вашей машине. Оборачивая ее в подстановку команды $(...), ее вывод будет передан в make.

Возможно, у вас есть анекдотический опыт, когда использование количества ядер + 1 приводит к более быстрому времени компиляции. Это больше связано с такими факторами, как задержки ввода/вывода, другие задержки ресурсов и другая доступность ограничений ресурсов.

Чтобы сделать это с nproc, вам нужно будет объединить подстановку команды с арифметическим расширением $((...)):

make -j$(( $(nproc) + 1))

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

Компиляция 100 крошечных файлов C может быть быстрее, чем компиляция одного огромного, или наоборот. Компиляция небольшого, но очень запутанного кода может быть медленнее, чем компиляция большого количества стандартного/линейного кода.

Даже контекст сборки имеет значение — использование j factor, оптимизированного для сборок на выделенных серверах, точно настроенных для эксклюзивных, не пересекающихся сборок, может дать очень разочаровывающие результаты при использовании разработчиками, собирающими параллельно на одном и том же общем сервере (каждая такая сборка может занять больше времени, чем все они вместе взятые, если были бы выполнены последовательно) или на серверах с разной конфигурацией оборудования или виртуализированных.

Также есть аспект корректности спецификации сборки. Очень сложные сборки могут иметь условия гонки, вызывающие прерывистые сбои сборки с частотой, которая может сильно варьироваться при увеличении или уменьшении j facto r.

Я могу продолжать и продолжать. Суть в том, что вы должны действительно оценить свою сборку в вашем очень конкретном контексте, для которого вы хотите оптимизировать j factor. Комментарий @Jeff Schaller применим: итерация до тех пор, пока не найдете наилучший вариант. Лично я бы начал со значения nproc, попробовал бы сначала вверх и вниз только в том случае, если попытки вверх покажут немедленное ухудшение.

Возможно, стоит сначала измерить несколько идентичных сборок в предположительно идентичном контексте только для того, чтобы получить представление об изменчивости ваших измерений – если она слишком высока, это может поставить под угрозу все ваши усилия по оптимизации (изменчивость в 20% полностью затмит улучшение/ухудшение в 10% при поиске j factor).

Наконец, на мой взгляд, лучше использовать (адаптивный) менеджер задач, если он поддерживается и доступен, вместо фиксированного j factor – он последовательно обеспечивает лучшее выполнение сборки в более широком диапазоне контекстов.

Если вам нужно количество ядер процессора – 1, и команда lscpu не найдена, вы можете использовать это:

make -j$(grep processor /proc/cpuinfo | tail -n 1 | awk '{print $3}')

Если вы хотите написать команду make для использования такого количества параллельных исполнителей, как у вас есть виртуальных ЦП, я предлагаю использовать:

nproc | xargs -I % make -j%

Что можно записать либо как отдельную команду, либо как директиву RUN в Dockerfile (так как Docker не поддерживает вложенные команды)

lscpu | awk '/^CPU\(/{print $2}'

Это позволит вам узнать количество ядер процессора.

Ответ или решение

Определение оптимального значения параметра -j для компиляции с помощью make — это важная задача для программистов и инженеров, стремящихся повысить производительность своих компиляционных процессов. Правильное определение этой величины может существенно сократить время компиляции, но требует учёта ряда факторов, таких как количество доступных процессорных ядер, объём оперативной памяти, характер работы сборщика (зависимость от ЦП или ввода-вывода) и другие специфики системы.

Теория

Когда мы компилируем программное обеспечение с использованием инструмента make, параметр -j позволяет задать максимальное количество параллельных задач. Это может значительно повысить скорость компиляции, если ресурсы вашей машины (процессоры и память) могут обслуживать одновременно несколько процессов. На современных многопроцессорных системах эффективно использовать все доступные ядра для ускорения выполнения задач.

Команда nproc является простым инструментом, который возвращает количество доступных процессорных ядер для текущего процесса, что обычно соответствует количеству ядер процессора. Это, конечно, базовая отправная точка для установки значения -j, так как теоретически мы можем использовать столько параллельных задач, сколько у нас есть процессорных ядер.

Однако, эта теория имеет свои нюансы. На практике число процессов, которые можно запускать параллельно, зависит не только от процессорных ядер, но и от оперативной памяти и особенностей конкретного сборочного процесса. Многие компиляции могут быть ограничены не только ЦПУ, но и ввода-вывода, а также количеством доступной оперативной памяти, что делает оптимизацию процесса компиляции более сложным заданием.

Примеры

Обычная практика начинается с использования make -j$(nproc), чтобы задействовать все доступные ядра. Часто прибавляют ещё одно ядро, например, с использованием make -j$(( $(nproc) + 1)), чтобы компенсировать простои, возникающие по причинам ввода-вывода или ожидания.

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

Некоторые системы или проекты требуют ещё более сложной настройки. В таких случаях можно рассматривать использование специализированных скриптов, например, adaptively tuned job servers, которые автоматически подбирают оптимальное число процессов для текущей системы на основе характеристик нагрузки и доступных ресурсов.

Применение

Чтобы выбрать подходящее число задач для режима -j, следует начать с оценки ресурсов вашей системы и особенностей компиляции. Вы можете использовать сценарии для автоматического расчёта числа параллельных задач. Например, в Linux можно выполнять следующие команды для определения числа процессорных ядер:

nproc
lscpu | awk '/^CPU\(/{print $2}'

Для более тонкой настройки, помимо фиксированного числа, которое выводит nproc, может быть полезно исследовать особые скрипты, способные оценивать память и вводить коррективы. Командные сценарии могут выглядеть так:

make -j$(nproc)

Либо:

make -j$(grep processor /proc/cpuinfo | tail -n 1 | awk '{print $3}')

Если ваша система поддерживает более сложные оперативные настройки, стоит рассмотреть возможности их применения. Например, с использованием временных файловых систем (tmpfs), которые могут уменьшить зависимость от диска и сделать процесс более процессоро-интенсивным, тем самым удваивая эффективность использования многопоточности.

Ключ к успешной оптимизации — это эксперименты и мониторинг. Изучение производительности через множество пробных сборок поможет вам точно настроить число задач, запускаемых параллельно. Помните, что увеличение числа параллельных задач сверх определённого предела может привести к пагинации памяти, снижению производительности, а даже к нестабильности сборки, по причине так называемых "гонок" (race conditions).

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

Оцените материал
Добавить комментарий

Капча загружается...