G ++ намного медленнее для нескольких файлов, чем для монолитного отдельного файла с использованием Google Mock - PullRequest
0 голосов
/ 14 февраля 2019

У меня проблема, связанная с g ++.По сути, g ++ занимает значительно больше времени для компиляции программы, когда она разбита на несколько файлов, а не на один монолитный файл.Фактически, если вы соберете отдельные файлы вместе и скомпилируете их, это будет выполняться намного быстрее, чем если бы вы перечислили отдельные файлы в командной строке g ++.Например, для девяти файлов сборка занимает 1 минуту 39 секунд;когда я соединяю их вместе, компиляция занимает всего 13 секунд.Я пытался использовать strace, но он просто застрял в cc1plus;когда я использую опцию -f, я все еще не могу разобраться, что является причиной проблемы.

Я выделил проблему.Вот как это воспроизвести.Я написал очень простую программу, например, так:

void func_01(int i) 
{
  int j;
  volatile int *jp;

  jp = &j;

   for (; i; i--) ++*jp;
}

void call_01(void)
{
  func_01(10000);
}

int main(int argc, char *argv[])
{
  call_01();
}

Затем я скопировал ее, удалив основной и подставив все увеличивающиеся числа, 999 раз.Затем я построил:

% time g++ -c test*.cpp

real    0m18.919s
user    0m10.208s
sys     0m5.595s
% cat test*.cpp > mon.cpp
% time g++ -c mon.cpp  

real    0m0.824s
user    0m0.776s
sys     0m0.040s

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

Версия g ++ - 4.4.2, но я проверил последнюю версию 8.2.0, и она там тоже существует.

Ответы [ 2 ]

0 голосов
/ 15 февраля 2019

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

Я провел сравнение с программой cat, которая выводит результаты в / dev / null.Для cat test_*.cpp >/dev/null это заняло ~ 0,008 с, а для cat mon.cpp >/dev/null ~ 0,001 с.Это почти в 10 раз больше для 999 файлов.Добавьте к этому, что компилятор также должен настроить некоторое внутреннее управление для каждого файла, который он компилирует, что делается только один раз для большого монолитного случая.

Но так как другие ответили, что настройка сборки в системе сборки, какmake или ninja, разница становится очевидной, если коснуться только одного файла из множества по сравнению с монофайлом.Для восстановления с использованием ниндзя потребовалось 1.196s для моно-случая, но только 0.233s для случая с 999.

Примечание: в этих числах нет явных заголовочных файлов.

0 голосов
/ 14 февраля 2019

Существует два различных эффекта:

  1. Затраты на вызов компилятора: компиляторы являются сложными исполняемыми файлами, а иногда они даже разделяются на фронтенд и исполняемый модуль бэкэнда, а внешний интерфейс порождает бэкэнд для каждогоотдельный исходный файл, даже когда все исходные файлы передаются в один и тот же вызов компилятора из внешнего интерфейса.Gcc и llvm делают это, например.

    • Укажите g++ -v, чтобы увидеть эти избыточные вызовы компилятора.Это ответ на ваш главный вопрос. Я думаю о том, почему это происходит даже без заголовочных файлов.
  2. Издержки из-за многократного анализа и компиляции одних и тех же заголовочных файлов с нуля.В реальных примерах эти издержки заголовка будут гораздо более значительными, чем сам вызов компилятора.

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

Да!И это также может быть в 1000 раз медленнее, чем в 5 раз.При использовании кода с интенсивным использованием шаблонов компилятору необходимо многое сделать во время компиляции.

Замедление при разделении на множество исходных файлов особенно сильно сказывается на коде C ++, поскольку C ++ интенсивно использует заголовки .Все ваши исходные файлы *.cpp скомпилированы отдельно, и все заголовки, которые они включают, включены избыточно для каждого отдельного исходного файла.

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

Количество исходных файлов для рукописного исходного кода C ++код, а также для сгенерированного исходного кода C ++ является компромиссом между:

  1. Мое полное время перестроения быстрое, но мое инкрементное время сборки медленное.

    • Этоэто тот случай, когда у вас есть только один исходный файл (то есть * .cpp файлы) или очень мало исходных файлов.
  2. Полное время сборки у меня медленное, но время инкрементной сборкиэто быстро.

    • Это тот случай, когда у вас есть много небольших исходных файлов (то есть файлов * .cpp).

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

за 1. полныйВремя компиляции с нуля короткое, так как компилятор видит все заголовки только один раз, что важно в C ++, особенно с библиотеками на основе только заголовков (или с интенсивными заголовками), такими как STL или boost.

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

Это сильно зависит от вашего варианта использования.

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

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