Сборка приложений из исходников процесс для юниксоида обычный и даже обыденный. Мы компилируем приложения, чтобы опробовать новый функционал, наложить патчи или просто установить пакет в систему (привет поклонникам BSD и Gentoo). Однако современные приложения настолько велики, что порой этот процесс может занять не один час. Чтобы не ждать так долго, предлагаю воспользоваться специальными утилитами, которые могут существенно сократить время сборки приложений.
Вообще, способов повышения скорости сборки приложений в никсах несколько, и не все они требуют использования дополнительного софта. Один из наиболее простых и прямолинейных способов заключается в отключении любых флагов оптимизации. Этот шаг действительно дает существенный выигрыш в скорости сборки, но также приводит к заметному снижению скорости исполнения скомпилированного софта. Если ты согласен с таким положением вещей, то можешь приказать компилятору принудительно отключать любые оптимизации. Для этого добавь следующие строки в свой ~/.bashrc:
export CFLAGS='-O0'
export CXXFLAGS=$CFLAGS
Пользователи FreeBSD и Gentoo могут использовать файл /etc/make.conf для изменения значения этих переменных:
CFLAGS='-O0'
CXXFLAGS='-O0'
В OpenBSD файл носит имя /etc/mk.conf.
Ускорить процесс компиляции можно с помощью распараллеливания, то есть одновременной компиляции сразу нескольких файлов, содержащих исходный текст. Те, кто знаком с программированием, должны знать, что любая более-менее крупная программа, написанная на языке Си, состоит из нескольких обособленных файлов, содержащих фрагменты исходного кода. Во время сборки программы эти файлы обрабатываются компилятором независимо и лишь в самом конце объединяются линковщиком в единый исполняемый файл. Благодаря этой особенности сборку приложения можно распараллелить так, чтобы эти фрагменты-файлы компилировались одновременно.
Подавляющее большинство приложений с открытым кодом используют make в качестве утилиты, контролирующей все этапы сборки. Make принимает флаг '-j', позволяющий задать количество одновременных потоков компиляции (на самом деле, он указывает на количество параллельно выполняемых задач, но мы не будем вдаваться в подробности). Правильно устанавливая значение этого флага, мы можем существенно повысить скорость сборки приложения на многоядерных процессорах. Так, если твой камень четырехъядерный, то оптимальное значение флага '-j' будет равно… пяти. Все верно, опытным путем доказано, что «лишний» поток еще больше ускоряет процесс. Причем то же верно и для одноядерных систем, для которых рекомендуемым значением будет 2.
В «классических линуксах» флаг '-j' следует указывать прямо во время сборки приложения:
$ ./configure
$ make -j5
$ sudo make install
В системах, основанных на портах, есть специальные опции конфигурационного файла, позволяющие задействовать этот флаг глобально. Например, в Gentoo любые опции make можно прописать в переменную MAKEOPTS файла /etc/make.conf. Во FreeBSD эта переменная носит имя MAKE_ARGS.
В тех же «source based» дистрибутивах можно задействовать другой трюк, позволяющий сократить время сборки в среднем на 10%. Смысл его в том, чтобы поместить каталог, используемый для хранения промежуточных результатов компиляции, в tmpfs. Скорость доступа к файлам существенно возрастет, что и приведет к выигрышу. Большинство систем использует для хранения временных файлов один из подкаталогов /var/tmp, поэтому нам достаточно просто примонтировать к нему tmpfs. Пример для Gentoo:
$ sudo mount -t tmpfs tmpfs -o size=1G,nr_inodes=1M
/var/tmp/portage
Естественно, машина должна обладать достаточным объемом памяти (2 Гб вполне хватит), чтобы tmpfs не начала использовать swap, что еще более растянет процесс компиляции. Если же памяти в системе мало, то ее можно освободить с помощью завершения всех «толстых» приложений и выхода из иксов.
Кэширование
По умолчанию компилятор не обращает внимания на ранее скомпилированные файлы и производит полную перекомпиляцию всего приложения во время повторного запуска процесса сборки. Это может сильно навредить, если ты, например, многократно исправляешь один из файлов исходников и каждый раз после этого инициируешь сборку. Это также увеличит время экспериментов с ядром, когда ты несколько раз пересобираешь ядро, пробуя включать и выключать различные опции. Для решения проблемы можно воспользоваться услугами ccache.
Программа ccache – это кэширующий препроцессор, который позволяет избежать повторной компиляции уже скомпилированных ранее файлов. Он генерирует хеш для каждого компилируемого файла, поэтому если с момента предыдущей компиляции файл остается неизменным, его повторная обработка не производится, а используется уже ранее скомпилированный объектный файл. Ccache позволяет существенно поднять скорость повторной компиляции приложения. В некоторых случаях прирост может составлять десятки раз.
Для задействования ccache достаточно установить его в систему и запускать процесс сборки приложения с помощью примерно такой команды:
$ CC="ccache gcc" CXX="ccache g++" ./configure
Закэшированные препроцессором объектные файлы хранятся в каталоге ~/.ccache. По умолчанию его размер ограничен 1 Гб, но я бы порекомендовал увеличить его до 4 Гб:
$ ccache -M 4G
Если места в домашнем каталоге будет недостаточно для хранения кэша, ты можешь указать ccache использовать другой каталог:
$ echo "export CCACHE_DIR=\"/var/tmp/
ccache/\"" >> ~/.bashrc
Для верности создай симлинк:
$ rm -rf ~/.ccache
$ ln -s /var/tmp/ccache ~/.ccache
Особое место ccache занимает в системах, основанных на портах. Компиляция утилит, необходимых только во время сборки порта, статических библиотек и других «одноразовых» инструментов, в таких системах обычно осуществляется каждый раз во время сборки порта, их требующего.
Поэтому ccache приносит неоценимую пользу. К тому же, уже существующие в кэше файлы могут быть повторно использованы при обновлении портов. Ниже приводится два рецепта использования ccache: в дистрибутиве Gentoo и в ОС FreeBSD.
Для активации ccache в Gentoo достаточно выполнить два простых действия:
1. Установить ccache:
$ sudo emerge -av ccache
2. Отредактировать файл /etc/make.conf:
$ sudo vi /etc/make.conf
# Активируем ccache
FEATURES="ccache"
# Место хранения кэша
CCACHE_DIR="/var/tmp/ccache/"
# Размер кэша
CCACHE_SIZE="4G"
После выполнения этих действий все порты будут собираться с использованием ccache. Чтобы активировать ccache во время сборки ядра с помощью genkernel, следует использовать следующую команду:
$ sudo genkernel --kernel-cc=/usr/lib/
ccache/bin/gcc --menuconfig all
В случае с FreeBSD все несколько сложнее, но вполне в рамках разумного:
1. Устанавливаем ccache:
$ cd /usr/ports/devel/ccache
$ sudo make install clean
2. Редактируем /etc/make.conf:
$ sudo vi /etc/make.conf
# Если переменная окружения NO_CACHE не
определена, активируем ccache
.if !defined(NO_CACHE)
CC=/usr/local/libexec/ccache/world-cc
CCX=/usr/local/libexec/ccache/world-c++
.endif
# Отключаем ccache при сборке самого себя
.if ${.CURDIR:M*/ports/devel/ccache}
NO_CCACHE=yes
.endif
3. Редактируем ~/.cshrc (или ~/.bashrc):
$ sudo vi ~/.cshrc
# Стандартные переменные ccache
setenv PATH /usr/local/libexec/ccache:$PATH
setenv CCACHE_PATH /usr/bin:/usr/local/bin
setenv CCACHE_DIR /var/tmp/ccache
setenv CCACHE_LOGFILE /var/log/ccache.log
# Устанавливаем размер кэша
if ( -x /usr/local/bin/ccache ) then
/usr/local/bin/ccache -M 4G > /dev/null
endif
Для отключения ccache во время сборки определенного порта используем такую команду вместо стандартного «make install clean»:
$ sudo make NO_CACHE=yes install clean
Распределенная компиляция
Увеличить скорость компиляции можно с помощью привлечения к этому процессу других машин. Я не говорю сейчас о сборке приложения на более мощной машине с последующим копированием результата на менее производительную (хотя это тоже вариант), я говорю об организации кластера из машин, каждая из которых будет принимать участие в сборке одного приложения. Поднять такой кластер достаточно просто, и для этого мы воспользуемся инструментом под названием distcc.
Distcc – это обертка для gcc, которая позволяет задействовать мощности других машин для увеличения скорости компиляции приложения. В отличие от других решений этого класса, distcc прост в установке и не требует модификации исходного кода приложения или каких-либо подготовительных действий. На удаленную машину передается уже обработанный препроцессором исходный код, поэтому со своей стороны она не должна иметь каких-либо заголовочных файлов или библиотек для проведения успешной компиляции.
Кластер distcc обычно состоит из одного клиента и нескольких серверов. Клиент запускается на машине, инициирующей процесс сборки, и передает задания на выполнение компиляции серверам, работающим на удаленных машинах. Задание состоит из обработанного препроцессором файла, содержащего исходный код, и флагов компилятора. Все это отправляется на сервер с помощью обычного сетевого соединения без какого-либо шифрования и аутентификации удаленной стороны. Серверы, фактически выполняющие компиляцию, могут работать под управлением операционных систем Linux, *BSD, Solaris или Windows (потребуется установка gcc и distcc в окружение cygwin), однако версия gcc на этих машинах должна быть одинаковой.
Нет смысла использовать distcc для одноразовой сборки приложения, с этим вполне справится и один комп. Зато в дистрибутиве Gentoo и ОС FreeBSD он может дать заметный прирост в скорости установки и обновления приложений. Gentoo уже подготовлен для использования distcc, поэтому его владельцам достаточно выполнить несколько простых шагов для активации обертки. Сначала установим distcc на все машины кластера:
$ sudo emerge distcc
Далее переходим на машину-клиент и редактируем файл /etc/make.conf:
$ sudo vi /etc/make.conf
# Указываем количество потоков компиляции
MAKEOPTS="-j8"
# Активируем distcc
FEATURES="distcc"
# Каталог для хранения кэша distcc
DISTCC_DIR="/tmp/.distcc"
В опции MAKEOPTS указываем общее количество потоков компиляции. Обычно это число высчитывается следующим образом: общее количество процессоров/ядер на всех машинах * 2 + 1. Однако это не всегда верно, и, возможно, придется поэкспериментировать, чтобы подобрать оптимальное значение.
Для задания хостов, участвующих в компиляции, воспользуемся утилитой distcc-config (на самом деле, она всего лишь модифицирует значение переменной DISTCC_HOSTS):
$ sudo distcc-config --set-hosts "127.0.0.1
192.168.0.1 192.168.0.2 192.168.0.3"
Вместо IP-адресов можно использовать DNS-имена машин. Чтобы более равномерно распределить нагрузку по машинам, через слэш после адреса машины ты можешь указать одновременное количество заданий, которое она может принять. Например, если на машине 192.168.0.1 установлен четырехъядерный процессор, то общее количество заданий для нее лучше установить в пять: 192.168.0.1/5. Далее на всех машинах необходимо запустить сервер. Для этого отредактируем файл /etc/conf.d/distccd, чтобы разрешить выполнение заданий, полученных от клиентов нашей подсети:
DISTCCD_OPTS="${DISTCCD_OPTS} -allow 192.168.0.0/24"
Теперь добавим сервер в дефолтовый уровень запуска и запустим его:
$ sudo rc-update add distccd default
$ sudo /etc/init.d/distccd start
Теперь все порты будут компилироваться в кластере distcc. Чтобы собрать ядро с использованием distcc, используй следующую команду:
$ sudo genkernel --kernel-cc=distcc all
В отличие от Gentoo, FreeBSD не обладает инфраструктурой, необходимой для прозрачного внедрения distcc, поэтому нам придется вживлять его хирургическим путем. Для этого установим distcc с помощью системы портов:
$ cd /usr/ports/devel/distcc
$ sudo make install clean
Это нужно сделать на всех машинах, которые будут участвовать в процессе компиляции. Далее активируем сервер distcc в /etc/rc.conf так, чтобы он запускался во время инициализации ОС и мог принимать задания от клиента:
distccd_enable="YES"
distccd_flags="--nice 5 --allow 192.168.1.0/24
--daemon --user distcc -P /var/run/distccd.pid"
Этот шаг также необходимо выполнить на всех машинах, включая клиентскую (чтобы компиляция происходила и на машине, ее инициировавшей). Флаг '--allow' задает подсеть, машины которой имеют право отправлять задания серверу. Запускаем сервер:
$ sudo /usr/local/etc/rc.d/distccd start
Возвращаемся на клиентскую машину. Открываем файл /etc/make.conf и добавляем в него следующие строки:
# vi /etc/make.conf
# Использовать distcc в качестве компилятора
CC = distcc
CXX = distcc
# Количество задач
MAKE_ARGS =- j8
Мы указали distcc в переменных CC и CXX, благодаря чему порты и мир будут автоматически собираться в кластере, для распределения сборки всего остального пойдем на небольшой трюк и подменим стандартные компиляторы gcc и g++:
# mkdir -p /usr/local/lib/distcc/bin
# cd /usr/local/lib/distcc/bin
# ln -s /usr/local/bin/distcc gcc
# ln -s /usr/local/bin/distcc g++
# ln -s /usr/local/bin/distcc cc
# ln -s /usr/local/bin/distcc c++
Откроем /root/.cshrc и поместим созданный нами каталог в начала путей поиска бинарников:
setenv PATH /usr/local/lib/distcc/bin:$PATH
Туда же поместим инициализацию переменной DISTCC_HOST, которая будет содержать адреса distcc-серверов:
setenv DISTCC_HOSTS "127.0.0.1 192.168.1.2
192.168.1.3 192.168.1.4"
Все, для проверки можно запустить сборку какого-либо порта. Пакет distcc поставляется вместе с утилитой для мониторинга процесса сборки distccmon-text. Запустив программу без параметров, ты увидишь на экране текущее состояние distcc. Чтобы получать сведения в режиме реального времени, используй команду «distccmontext N», где N – это интервал в секундах между выводом информации на экран. Пользователи Gnome (и не только) могут воспользоваться графическим монитором под названием distccmon-gnome.
Не все порты могут быть прозрачно собраны с использованием distcc. В Gentoo они явно помечены как «нераспределяемые», поэтому сборка будет происходить только на машине-клиенте. Во FreeBSD все сложнее, здесь нет интеграции с distcc, поэтому при возникновении проблем со сборкой первым делом следует отключить distcc, закомментировав строку опции CC и CXX в /etc/make.conf и убрав /usr/local/ lib/distcc/bin из путей поиска бинарников.
Компиляция в облаке
Программа distcc становится эффективным решением, когда речь заходит о сборке приложения в кластере машин, размещенных в локальной сети, но в силу своей незащищенности она не может быть применена для распределения компиляции по машинам, разбросанным по всему миру. Ты можешь создать частную VPN, чтобы обойти эту проблему, но я предлагаю более простое и эффективное решение – SSH-туннель.
Все нижеприведенные инструкции актуальны для дистрибутива Gentoo, но не составит большого труда подогнать их под другой дистрибутив/ОС.
Для начала активируем пользователя distcc. Он автоматически создается во время установки порта, но не может выполнять вход в систему. Чтобы организовать туннель между машинами, нам нужно это исправить. Во-первых, пользователь должен иметь домашний каталог (пусть будет /etc/distcc):
# mkdir -p /etc/distcc/.ssh
# usermod -d /etc/distcc distcc
Во-вторых, ему должен быть назначен шелл:
# usermod -s /bin/bash distcc
В-третьих, он должен быть разлочен:
# passwd -u distcc
Эти три шага необходимо проделать на всех машинах. Чтобы клиент мог логиниться на машины-рабочие с использованием ssh и
отдавать задания distcc, он должен иметь ключ:
# ssh-keygen -t dsa -f /etc/distcc/.ssh/id_dsa
Публичный ключ (id_dsa.pub) необходимо скопировать в каталог /etc/distcc/.ssh/authorized_keys всех машин, которые будут участвовать в компиляции. Чтобы ssh и portage смогли работать совместно, придется исправить права доступа. На машинах-рабочих выполняем команды:
# chown -R distcc:daemon /etc/distcc
# chmod 644 /etc/distcc/.ssh/authorized_keys
На клиенте выполняем команды:
# chown portage:portage /etc/distcc/.ssh/id_dsa
# chmod 600 /etc/distcc/.ssh/id_dsa
# chmod 644 /etc/distcc/.ssh/id_dsa.pub
Чтобы во время выполнения emerge ssh не застрял на полпути с вопросом о принятии публичного ключа каждой машины-рабочего, заранее соберем все эти ключи:
# ssh-keyscan -t rsa рабочий1 рабочий2 рабочий3 \
> /var/tmp/portage/.ssh/known_hosts
# chown portage:portage /var/tmp/portage/.ssh/ \
known_hosts
Теперь создадим обертку для distcc:
# vi /etc/distcc/distcc-ssh
#!/bin/bash
exec /usr/bin/ssh -i /etc/distcc/.ssh/id_dsa "$@"
Сделаем ее исполняемой:
# chmod a+x /etc/distcc/distcc-ssh
Отредактируем файл /etc/make.conf:
# vi /etc/make.conf
MAKEOPTS="-j8"
FEATURES="distcc"
DISTCC_SSH="/etc/distcc/distcc-ssh"
DISTCC_HOSTS="localhost/2 distcc@рабочий1/3 distcc@
рабочий2/5"
Это все.
Удачной компиляции
Такие программы, как ccache и distcc, вкупе с другими трюками, могут дать весьма существенный прирост в скорости сборки, однако ты должен иметь в виду, что не все приложения могут быть собраны с их помощью. Сборка некоторых сложных программ, состоящих из многих компонентов, часто завершается ошибкой, причину которой трудно идентифицировать. Поэтому будь внимателен, используй последнюю версию ccache и одинаковые версии gcc на машинах distcc-кластера, кроме того, не забывай заглядывать в файл INSTALL.
Info
- В дистрибутиве Gentoo различные флаги компиляции портов можно активировать индивидуально для каждого порта (см. файл /etc/portage/package. cflags). Это можно использовать для принудительного отключения ccache и distcc при сборке определенных портов.
- Присвоив опции PORTAGE_NICENESS файла /etc/make. conf в дистрибутиве Gentoo слишком большое значение, ты можешь собственноручно сильно замедлить скорость сборки приложений.
- Программы ccache и distcc отлично уживаются вместе. В Gentoo ты можешь активировать их одновременно, добавив строку FEATURES="ccache distcc" в файл /etc/make.conf.