Программирование ARM SCons: кэширование файлов построения Sat, December 07 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


SCons: кэширование файлов построения Печать
Добавил(а) microsin   

В больших проектах ПО, над которым трудятся несколько разработчиков, можно иногда ускорить выполнение каждой сборки, если позволите использовать общий кэш от файлов построения. Так или иначе редко бывает, что в процессе разработки сразу меняется более чем несколько результирующих файлов, большинство файлов не меняются. Использование кэша также может помочь и индивидуальному разработчику: например, если вы хотите начать работу над новой функцией ПО в чистом дереве проекта, то те артефакты сборки, которые могут быть использованы повторно, могут быть взяты из кэша, чтобы наполнить дерево сборки, экономя время начальной сборки. SCons делает это просто и надежно.

Указание директории Derived-File Cache. Чтобы разрешить кэширование производных файлов, используйте функцию CacheDir в любом файле SConscript:

CacheDir('/usr/local/build_cache')

Директория кеша, которая указана здесь в параметре, должна быть доступна на чтение и запись для всех разработчиков, кто будет использовать кэшированные файлы (если используется --cache-readonly, то требуется доступ только на чтение). Также это место кэша должно быть как-то централизовано, чтобы все системы сборки могли к нему обращаться. В рабочих окружениях, где разработчики используют отдельные системы (наподобие индивидауальных рабочих станций) для выполнения сборки, эта директория обычно должна быть расшарена и смонтирована через сетевую файловую систему NFS (Network File System). Хотя SCons при необходимости создаст указанную директорию для кэша, в этом многопользовательском сценарии как правило лучше будет её создать предварительно, чтобы можно было корректно настроить права доступа для всех участников сборки.

Вот что здесь происходит: если для сборки указана CacheDir, то каждый раз при сборки файла он сохраняется в эту директорию кэша индексированным своей сигнатурой сборки. При последующих сборках перед вызовом действия, запускаемого для сборки файла, вычисляется сигнатура сборки, и SCons проверяет директорию кэша, чтобы обнаружить, совпала ли вычисленная сигнатура с сигнатурой файла предыдущей сборки. Если было совпадение сигнатур, то локальная сборка файла не производится, вместо этого из кеша результирующий файл копируется в локальную директорию сборки, примерно так:

% scons -Q
cc -o hello.o -c hello.c
cc -o hello hello.o
% scons -Q -c
Removed hello.o
Removed hello
% scons -Q
Retrieved `hello.o' from cache
Retrieved `hello' from cache

Обратите внимание, что функция CacheDir требует, чтобы была вычислена сигнатура сборки, даже если вы сконфигурировали SCons на использование меток времени для обнаружения актуальности результирующего файла (см. выше главу "Зависимости" для информации по функции Decider), поскольку сигнатура сборки используется для того, чтобы определить наличие и актуальность target-файла в кэше. Как следствие, использование CacheDir может уменьшить или вовсе устранить выгоду от оптимизации прозиводительности, когда для принятия решения об актуальности целей были настроены метки времени.

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

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

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

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

% scons -Q
cc -o hello.o -c hello.c
cc -o hello hello.o
% scons -Q -c
Removed hello.o
Removed hello
% scons -Q --cache-show
cc -o hello.o -c hello.c
cc -o hello hello.o

Недостаток такого компромисса очевиден - вы больше не знаете, была ли взята цель сборки из кэша, либо она была собрана локально.

Отключение Derived-File Cache для определенных файлов. Вы можете захотеть в своей конфигурации, чтобы для определенных файлов кэширование было запрещено. Если хотите поместить исполняемые файлы только в центральный кэш, но не в промежуточные объектные файлы, то можете испозовать функцию NoCache, чтобы указать, что объектные файлы не должны кэшироваться:

env = Environment()
obj = env.Object('hello.c')
env.Program('hello.c')
CacheDir('cache')
NoCache('hello.o')

Тогда при запуске scons после очистки собранных целей будет произведена перекомпиляция объектного файла локально (польскольку его нет в директории кэша производных файлов), но при этом следует понимать, что каталог кэша производных файлов содержит актуальную исполняемую программу, которая может быть извлечена вместо повторной линковки:

% scons -Q
cc -o hello.o -c hello.c
cc -o hello hello.o
% scons -Q -c
Removed hello.o
Removed hello
% scons -Q
cc -o hello.o -c hello.c
Retrieved `hello' from cache

Запрет Derived-File Cache. Извлечение уже собранного файла из кэша производных файлов обычно значительно экономит время по сравнению пересборкой файла, то величина такой экономии (или даже есть ли экономия времени вообще) может в значительной мере зависеть от вашей системы и конфигурации сети, через которую распространяется кэш. Например, извлечение кэшированных файлов из загруженного сервера или через загруженную (медленную) сеть может быть дольше, чем локальная пересборка файла цели.

В таких случчаях вы можете указать опцию командной строки --cache-disable, чтобы SCons не извлекала из директории кэша уже собранные файлы:

% scons -Q
cc -o hello.o -c hello.c
cc -o hello hello.o
% scons -Q -c
Removed hello.o
Removed hello
% scons -Q
Retrieved `hello.o' from cache
Retrieved `hello' from cache
% scons -Q -c
Removed hello.o
Removed hello
% scons -Q --cache-disable
cc -o hello.o -c hello.c
cc -o hello hello.o

Заполнение Derived-File Cache уже собранными файлами. Иногда у вас может быть один или несколько производных файлов, которые уже имеются в вашем дереве локальной сборки, которые вы хотели бы предоставить другим разработчикам, которые тоже выполняют сборки. Например, можно более эффективно выполнять сборки интеграции с отключенным кэшем (как было описано в предыдущей секции) и только лишь заполнять директорию кэша производными файлами после того, как интеграционная сборка была полностью завершена. Таким образом, кэш будет заполнен только производными файлами, которые являются частью полного, успешного построения, а не файлами, которые впоследствии могут быть перезаписаны во время отладки проблем интеграции.

В этом случае вы можете использовать опцию --cache-force, чтобы SCons поместила все производные файлы в кэш, даже если файлы уже существуют в локальном дереве из-за предыдущего вызова:

% scons -Q --cache-disable
cc -o hello.o -c hello.c
cc -o hello hello.o
% scons -Q -c
Removed hello.o
Removed hello
% scons -Q --cache-disable
cc -o hello.o -c hello.c
cc -o hello hello.o
% scons -Q --cache-force
scons: `.' is up to date.
% scons -Q
scons: `.' is up to date.

Обратите внимание на то, как в этом примере запуска демонстрируется, что опция --cache-disable избегает помещения в кэш собранных файлов hello.o и hello, но после использования опции --cache-force файлы помещаются в кэш при следующем запуске, чтобы они впоследствии извлекались всеми, кто использует кэш.

Минимизация конкуренции на кэше: опция --random. Если вы разрешили нескольким сборкам одновременно обновлять один и тот же кэш производных файлов (derived-file cache directory), то запущенные в одно и то же время сборки начнут конкуренцию при обращении к одним и тем же файлам в одном и том же порядке. Если, например, вы линкуете несколько файлов в исполняемую программу:

Program('prog', ['f1.c', 'f2.c', 'f3.c', 'f4.c', 'f5.c'])

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

% scons -Q
cc -o f4.o -c f4.c
cc -o f2.o -c f2.c
cc -o f3.o -c f3.c
cc -o f1.o -c f1.c
cc -o f5.o -c f5.c
cc -o prog f1.o f2.o f3.o f4.o f5.o

Однако если две такие сборки будут просматривать кэш одновременно, что они обе могут решить, что надо пересобрать f1.o и вытолкнуть его в директорию кэша, и что надо пересобрать f2.o (и тоже поместить его в директорию кэша), и f3.o надо пересобрать, и так далее. Это не создаст фактически проблемы для сборки - обе сборки завершатся успешно, сгенерируют корректные выходные файлы и наполнят ими кеш - однако при этом будет произведена лишняя работа.

Чтобы смягчить конкурецию на кеше, можно использовать опцию командной строки --random, чтобы указать SCons собрать зависимости в случайном порядке:

% scons -Q --random
cc -o f3.o -c f3.c
cc -o f1.o -c f1.c
cc -o f5.o -c f5.c
cc -o f2.o -c f2.c
cc -o f4.o -c f4.c
cc -o prog f1.o f2.o f3.o f4.o f5.o

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

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

Если вы хотите, чтобы не надо было указывать всякий раз опцию --random в командной строке, то эту опцию можно включить функцией SetOption в любом файле SConscript:

SetOption('random', 1)
Program('prog', ['f1.c', 'f2.c', 'f3.c', 'f4.c', 'f5.c'])

Использование пользовательского класса директории кэширования. Внутренний класс CacheDir системы SCons может быть расширен для поддержки пользовательской реализации деталей поведения кэширования. Например, это может быть использование сжатых файлов кэша, шифрованных файлов кэша, накопление статистики и данных сборок, или какие-то другие аспекты.

Чтобы создать свой класс для кэширования, он должен быть произведен как дочерний (subclass) от класса SCons.CacheDir.CacheDir. Затем вы можете передать свой класс в метод CacheDir, или установить construction-переменную $CACHEDIR_CLASS в свой класс перед конфигурированием кэша в этом окружении. SCons внутри себя будет использовать ваш класс, когда происходят операции с кэшем. Пример ниже показывает простой случай использования, когда переопределяется метод copy_from_cache, чтобы записывать общее количество байт, извлеченных из кэша.

import SCons
import os
 
class CustomCacheDir(SCons.CacheDir.CacheDir):
    total_retrieved = 0
 
    @classmethod
    def copy_from_cache(cls, env, src, dst):
        # записать общее количество байт, взятых из кэша
        cls.total_retrieved += os.stat(src).st_size
        super().copy_from_cache(env, src, dst)
 
env = Environment()
env.CacheDir('scons-cache', CustomCacheDir)
# ...

[Ссылки]

1. SCons: руководство пользователя, быстрый старт.
2. SCons: дополнительные возможности.

 

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


Защитный код
Обновить

Top of Page