Распределенная компиляция

Утеха программиста

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

Арпан Сен, технический директор, Synapti Computer Aided Design Pvt Ltd

Арпан Сен (Arpan Sen) – ведущий инженер, работающий над разработкой программного обеспечения в области автоматизации электронного проектирования. На протяжении нескольких лет он работал над некоторыми функциями UNIX, в том числе Solaris, SunOS, HP-UX и IRIX, а также Linux и Microsoft Windows. Он проявляет живой интерес к методикам оптимизации производительности программного обеспечения, теории графов и параллельным вычислениям. Арпан является аспирантов в области программных систем.



27.06.2011

Сокращение времени сборки для систем, основанных на C\C++, является одной из главных проблем, возникающих перед любым программистом по сборке или подготовке окончательного релиза. Эта статья рассматривает некоторые доступные опции утилит с открытым исходным кодом, которые ускоряют процесс сборки за счет распараллеливания — распределяя процесс сборки по множеству компьютеров локальной сети. Обсуждение в этой статье в основном фокусируется на GNU make в виду ее широкой доступности.

Опция -j в GNU make

GNU make по умолчанию — последовательная утилита. Она последовательно вызывает соответствующий компилятор, чтобы скомпилировать исходные коды C\C++. Как правило, исходные файлы C\C++ (обычно с расширениями .cpp или .cxx) могут быть собраны независимо друг от друга. Для этого make вызывается с опцией –j. В листинге 1 показано такое типичное использование.

Листинг 1: Типичный вызов GNU make
make –j10 –f makefile.x86_linux

Аргумент для –j— 10 — это максимальное число одновременных компиляций, которые могут возникнуть в результате запуска процесса сборки. Если для -j не задан аргумент, тогда все исходные файлы будут поставлены в очередь в системе для одновременной компиляции. Использование опции -j дает ощутимый эффект тогда, когда вы выполняете сборку на многоядерной системе. Чтобы получить выигрыш от опции -j, необходимо обратить внимание на некоторые ключевые проблемы; они обсуждаются в следующей секции.

Проблемы при использовании опции -j и их возможные решения

Проверим для начала конфигурацию системы. На системах с небольшим количеством памяти (менее 512 МБ ОЗУ) при запуске большого числа одновременных компиляций могут замедлить работу системы из-за необходимости подкачки. В таких случаях время компиляции увеличивается. Необходимо поэкспериментировать, чтобы выяснить оптимальное значение опции -j для вашей системы. Другая опция, которую можно использовать для инструментального средства GNU make совместно с опцией -j— это опция –l или –load-average, которая продолжает запускать задачи только если загрузка системы меньше определенного уровня.

Также можно использовать один и тот же временный файл для независимых компиляций. Рассмотрите фрагмент для make, показанный в листинге 2.

Листинг 2: Makefile с одинаковым временным файлом y.tab.c
my_parser : main.o parser1.o parser2.o
       g++ -o $* $>


parser1.o : parser1.y 
       yacc parser1.y
       g++ -o $* -c y.tab.c

parser2.o : parser2.y 
       yacc parser2.y
       g++ -o $* -c y.tab.c

Предположим, что грамматические файлы parser1.y и parser2.y располагаются в одной и той же директории. В процессе последовательной компиляции утилита yacc создаёт файл y.tab.c (где y.tab.c это имя файла по умолчанию) для parser1.y и затем для parser2.y; но в параллельном режиме это вызовет конфликт. Разрешить эту ситуацию можно несколькими путями: хранить два yacc файла в различных директориях, или использовать опцию –b, чтобы создать два различных вывода на C, как показано в листинге 3.

Листинг 3: Использование опции -b утилиты yacc для создания уникальных имен файлов
parser1.o : parser1.y 
       yacc parser1.y –b parser1
       g++ -o $* -c parser1.tab.c

Необходимо тщательно просмотреть makefile, чтобы обнаружить подобные ситуации, когда распараллеливание последовательного скрипта приводит к ошибкам.

Некоторые правила makefile имеют неявные зависимости. Рассмотрим ситуацию, показанную в листинге 4, где скрипт на языке Perl создает заголовочный файл, который включен другими исходными кодами.

Листинг 4: Makefile с неявными зависимостями
my_exe: info.h test1.o test2.o 
       g++ -o $@ $^ 

test1.o: test1.cxx 
       g++ -c $<

test2.o: test2.cxx 
       g++ -c $<

info.h: 
       make_header #скрипт на shell, генерирующий заголовочный файл

Файл info.h включен файлами test1.cxx и test2.cxx. При последовательной сборке make выполняет инструкции слева направо, и файл info.h создается первым. Тем не менее при параллельной сборке make может обрабатывать все зависимости параллельно — это потенциально приводит к периодическим сбоям в некоторых компиляциях, потому что файл info.h может быть не создан до начала компиляции файлов test1.cxx и test2.cxx. Чтобы устранить эту проблему, имеет смысл удалить info.h из списка зависимостей my_exe и поместить в списки зависимостей для test1.o и test2.o. Также целесообразно использовать оболочку, чтобы гарантировать что файл info.h создается только однажды. Листинг 5 показывает измененную версию скрипта make_header, а листинг 6 показывает makefile.

Листинг 5: Измененная версия скрипта make_header для устранения повторных записей
#!/usr/bin/bash

if [ -f info.h ]
then
  exit
fi

echo "#ifndef __INFO_H" > info.h
echo "#define __INFO_H" > > info.h

echo "#include <iostream>>" > > info.h
echo "using namespace std;" > > info.h
echo "int f1(int);" > > info.h
echo "int f2(int);" > > info.h

echo "#endif" > > info.h
Листинг 6: Измененная версия makefile из листинга 4
my_exe: info.h test1.o test2.o 
    g++ -o $@ $^ 

test1.o: test1.cxx info.h
    g++ -c $<

test2.o: test2.cxx info.h
    g++ -c $<

info.h: 
    make_header #скрипт на shell, генерирующий заголовочный файл

С помощью make-j можно добиться параллелизма, если должным образом создать makefile. Попробуйте избежать ненужных зависимостей в makefile везде, где это возможно.

Заметьте, что GNU make может получить параллелизм только для одной машины. Следующий раздел представляет distcc, инструмент, который позволяет вам разделить процесс сборки между множеством машин.

Знакомимся с distcc

Инструментальное средство distcc может распределять сборку C\C++ кода на множество машин. На каждой из этих машин должен быть установлен distcc Вот краткое руководство по установке и настройке:

  1. Скачиваем distcc (см. раздел ресурсы).
  2. Собираем distcc из исходных кодов на всех машинах, выполнив ./configure; make && make install.
  3. 1.На одной из машин стартует процесс сборки и затем распространяется на остальные машины (сервера). Запустите демон distcc на всех серверах (для этого необходимы привилегии root). distcc находится в каталоге /etc/init.d. Синтаксис запуска в режиме root:
    tcsh-arpan# /etc/init.d/distccd start

    И синтаксис запуска в режиме пользователя:
    tcsh-arpan$ sudo /etc/init.d/distccd

    Также можно запускать процессы демона distcc в пользовательском режиме, выполняя distccd –daemon –j N, где N— число задач, которые желательно запустить на данной машине.
  4. 1.Локальная машина должна знать, на какие серверы должен быть распределен процесс сборки. В зависимости от командного интерпретатора запускаем измененную версию команды:
    export DISTCC_HOSTS='localhost tintin asterix pogo'

    tintin, pogo и asterix — это другие хосты в сети, которые могут принимать процессы сборки; localhost соответствует локальной машине.
  5. Вместо использования директивы export также можно создать файл под именем hosts и поместить имена серверов, разделенные пробелами, в этот файл. Скопируйте файл в директорию $HOME/.distcc.

Как работает distcc?

distccdistccdistcc-jmakedistccdistcc

Теперь, когда distcc установлен, остается сделать только одно — запустить сборку. Вот вызов:

make –j4 CC=distcc –f makefile.x86_linux

Главное, что надо учитывать при работе с distcc

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

  • Машины должны иметь одинаковые конфигурации. Это означает, что на все машины должна быть установлена одинаковая версия g++, а также и все связанные с ним инструментальные средства, такие как ar, ranlib, libtool и другие. Также тип и версия операционной системы должна быть одинаковой.
  • Обработанный код с клиентской машины distcc отправляет на сервера. Необходимо убедиться, что на удаленной машине запущен процесс distccd daemon.
  • По умолчанию число задач, которые distcc планирует на одной машине, равняется "количеству процессоров + 2". Для одноядерной машины это число равняется 3. Помните об этом, запуская команду вида: make –j10 CC=distcc при трёх наличных хостах, что означает, что изначально только 9 задач компиляции будут запущены.
  • Убедитесь, что нижележащие машины имеют доступ к требуемой файловой системе, хранящей исходные файлы. В системах, основанных на NFS (Network File System) некоторые зоны - источники могут быть не смонтированы, что приведет к провалу компиляции. Также необходимо внимательно следить за загрузкой сети.
  • distcc используется для компиляции исходных кодов по сети. Шаги связывания не могут быть параллельны.

Как быть с частями процесса сборки, которые должны выполняться последовательно?

tcsh-arpan$ make –f make.init; 
make CC=distcc –j4 –f make.compile_x86; 
make –f make.link

Мониторинг процесса компиляции с distcc

В установочном пакете distcc имеется специальный инструмент мониторинга, работающего в консоли — distccmon-text. Перед тем как запустить процесс сборки, стоит открыть отдельное окно терминала и выполнить distccmon-text 5. После этого данный терминал будет непрерывно показывать состояние компиляции на множестве узлов в сети каждые 5 секунд. Листинг 7 показывает пример вывода в окне мониторинга.

Листинг 7: Вывод distccmon-text
2167  Compile     memory.c                    tintin[0]
2164  Compile     main.cxx                     tintin[1]
2192  Compile     ui_tcl.cxx                  asterix[0]
2187  Compile     traverse.c                  asterix[1]
2177  Compile     reports.cxx                  pogo[0]
2184  Compile     messghandler.c           pogo[1]
2181  Compile     trace.cpp                  localhost[0]
2189  Compile     remote.c                  localhost[1]

Использования ccache для дополнительного ускорения компиляции

Обычно, когда изменяются заголовочные файлы в структуре разработки на C\C++, среднестатистическая, основанная на make, система в итоге перекомпилирует все исходные файлы. Как правило, изменения в заголовочном файле затрагивают только некоторое подмножество файлов проекта, поэтому длительная по времени очистка и повторная сборка не требуется. Также можно использовать ccache— инструментальное средство, которое радикально снижает время, требуемое на очистку и полную сборку, обычно в 5-10 раз.

ccache действует как кэш-память для компилятора. Он создаёт хэш из пред-обработанных исходных файлов и опций компилятора, использованных для компиляции исходных кодов. Если в процессе повторной компиляции ccache не обнаруживает изменений в пред-обработанных исходных файлах и опциях компилятора, то он извлекает скомпилированную копию, которая была ранее сохранена в кэш-памяти программы. Это позволяет ускорить процесс компиляции.

Установка ccache

Чтобы скачать последнюю версию (2.4) ccache, обратитесь в раздел Ресурсы. В директории ccache выполните команду ./configure –prefix=/usr/bin, затем make && make install. Если ccache устанавливается в директорию, отличную от /usr/bin, то убедитесь что расположение ccache определено в переменной окружения PATH.

Переменные окружения ccache

Вот несколько переменных окружения, которые возможно использовать для настройки ccache:

  • CCACHE_DIR— Определяет директорию, где ccache хранит пред-компилированные файлы. Если эта переменная не определена, то по умолчанию кэширование будет происходить в директорию $HOME/.ccache.
  • CCACHE_TEMPDIR— определяет директорию, в которую ccache сохраняет временные файлы, которые он генерирует. Если эта переменная не определена, тогда по умолчанию используется директория $HOME/.ccache. Имеет смысл определить эту переменную и CCACHE_DIR, так как в большинстве компаний установлена квота для определенных областей файловой системы, и если к ним относится $HOME, то квота будет быстро исчерпана. Явное указание областей кэширования позволяет избежать этой проблемы.
  • CCACHE_DISABLE— если установлена, то ccache будет вызывать компилятор, пропуская ccache. Используется для диагностики.
  • CCACHE_RECACHE— если установлена, то ccache будет игнорировать существующие записи в кэше и вызывать компилятор; но для новых записей кэширует результат. Используется для диагностики.
  • CCACHE_LOGFILE— если установлена, то ccache будет записывать статистику попаданий и промахов в кэш в указанный файл. Очень полезно для диагностики.
  • CCACHE_PREFIX— добавляет префикс к командной строке, используемой ccache для правильного вызова компилятора. Это, в частности, используется для взаимодействия ccache с distcc. Как это происходит, описано детально в следующем разделе.

Использование ccache

Вы можете использовать ccache с distcc или без него. От опции -j он не зависит. Вот простейшее использование ccache: ccache g++ -o <executable name> <source file(s)>. При использовании с makefile переопределение переменной CC достаточно; см. листинг 8.

Листинг 8: Пример makefile, использующего переменную CC
CC := g++
app1: placer1.o route1.o floorplan1.o
    $(CC) –o $* $^ 
placer1.o: placer1.cxx
    $(CC) –o $* -c $<
…

Для makefile, показанного в листинге 8, синтаксис вызова make следующий:make "CC=ccache g++".

Чтобы использовать distcc с ccache, необходимо установить переменную окружения CCACHE_PREFIX для distcc, как показано далее: export CCACHE_PREFIX=distcc. (Данный синтаксис является верным для BASH. Если вы используете другой командный интерпретатор, то измените синтаксис соответственно).

Вот пример вызова make с ccache и distcc:

export CCACHE_PREFIX=distcc; make "CC=ccache g++" –j4 –f makefile.x86

Фактический вызов в командной строке в процессе выполнения выглядит следующим образом: ccache distcc –o placer1.o –c placer1.cxx и т.п. Заметьте, что ccache должен быть установлен только на одной машине. ccache выполняет первую проверку чтобы решить, достаточно ли копии в локальном кэше; иначе он «дает отмашку» для distcc на распределенную компиляцию.

Заключение

В этой статье мы рассмотрели инструментальные средства GNU make, distcc и ccache, которые могут помочь сделать процесс сборки параллельным. У этих инструментальных средств есть и несколько других функций, которые в состоянии дополнительно изменять их возможности в соответствии с требованиями, например, ccache имеет опцию –M, которая ограничивает размер кэша; а distcc имеет монитор с графическим интерфейсом, который называется distcc-gnome и отслеживает деятельность в процессе сетевой сборки (он создается, если была указана опция –use-gtk при сборке distcc). Ссылки в разделе Ресурсы предоставляют дополнительную информацию.

Ресурсы

Научиться

Получить продукты и технологии

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



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

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=AIX и UNIX
ArticleID=682642
ArticleTitle=Распределенная компиляция
publish-date=06272011