Определить функцию медленной компиляции

У меня есть некоторые файлы cpp, которые требуют многого для компиляции. Они содержат некоторые базовые классы/код с некоторыми шаблонами, но ничего не оправдывают время компиляции порядка десятков секунд.

Я использую пару внешних libs (boost/opencv)

Это то, что gcc говорит о времени компиляции. Как я могу найти вызов библиотеки /include/function, который виноват в ужасающем времени компиляции?

Execution times (seconds)
 phase setup             :   0.00 ( 0%) usr   0.00 ( 0%) sys   0.01 ( 0%) wall    1445 kB ( 0%) ggc
 phase parsing           :   6.69 (46%) usr   1.61 (60%) sys  12.14 (47%) wall  488430 kB (66%) ggc
 phase lang. deferred    :   1.59 (11%) usr   0.36 (13%) sys   3.83 (15%) wall   92964 kB (13%) ggc
 phase opt and generate  :   6.25 (43%) usr   0.72 (27%) sys  10.09 (39%) wall  152799 kB (21%) ggc
 |name lookup            :   1.05 ( 7%) usr   0.28 (10%) sys   2.01 ( 8%) wall   52063 kB ( 7%) ggc
 |overload resolution    :   0.83 ( 6%) usr   0.18 ( 7%) sys   1.48 ( 6%) wall   42377 kB ( 6%) ggc
...

Профилирование процесса компиляции С++ связано с определением медленного файла, но мне нужна более мелкая информация, чтобы найти виновника

(Другие файлы/проекты скомпилируются в миллисекундах/секундах, так что это не вопрос компьютерных ресурсов. Я использую gcc 4.9.1)

Ответ 1

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

Когда вы включаете слишком много заголовков и что эти заголовки содержат слишком много собственных заголовков, это просто означает, что у компилятора есть много работы, чтобы загрузить все эти файлы, и он потратит чрезмерное количество времени на обработке проходит, что он должен делать на весь код, независимо от того, действительно ли он используется или нет, например, перед обработкой, лексическим анализом, зданием АСТ и т.д. Это может быть особенно проблематичным, когда код распространяется на большое количество потому что производительность очень привязана к вводу/выводу (много времени тратилось только на извлечение и чтение файлов с жесткого диска). К сожалению, библиотеки Boost, как правило, очень структурированы таким образом.

Вот несколько способов или инструментов для решения этой проблемы:

  • Вы можете использовать инструмент include-what-you-use. Это инструмент анализа на основе Clang, который в основном рассматривает то, что вы на самом деле используете в своем коде, и из чего они берутся, и затем сообщает о любых возможных оптимизациях, которые вы могли бы сделать, удалив некоторые ненужные включения, используя вместо этого forward-declarations, или, возможно, заменить более широкие "все-в-одном" заголовки более мелкозернистыми заголовками.
  • В большинстве компиляторов есть опции для сброса предварительно обработанных источников (в настройках GCC/Clang, it -E или -E -P или просто используемая программа препроцессора GCC C cpp). Вы можете взять исходный файл и закомментировать различные включенные операторы или группы включенных операторов и выгрузить предварительно обработанный источник, чтобы увидеть общий объем кода, в который вставляются эти разные заголовки (и, возможно, использовать команду подсчета строк, например $ g++ -E -P my_source.cpp | wc -l), Это может помочь вам определить, в каком количестве строк кода обрабатывается, какие заголовки являются худшими нарушителями. Затем вы можете увидеть, что вы можете сделать, чтобы избежать их или как-то смягчить проблему.
  • Вы также можете использовать предварительно скомпилированные заголовки. Это функция, поддерживаемая большинством компиляторов, с помощью которых вы можете указать определенные заголовки (особенно содержащие тотал "все-в-одном" заголовки), которые должны быть предварительно скомпилированы, чтобы избежать повторного разбора их для каждого исходного файла, который их включает.
  • Если ваша ОС поддерживает его, вы можете использовать ram-disk для своего кода и заголовков ваших внешних библиотек. Это по существу занимает часть вашей оперативной памяти и делает ее похожей на обычный жесткий диск/файловую систему. Это может значительно сократить время компиляции за счет сокращения задержки ввода-вывода, поскольку все заголовки и исходные файлы считываются из оперативной памяти вместо фактического жесткого диска.

Вторая проблема заключается в создании экземпляров шаблонов. В своем отчете о времени от GCC должно быть указано значение времени для этапа создания экземпляра. Если это число будет высоким, и это будет, как только в коде будет задействовано значительное количество метапрограмм шаблонов, тогда вам нужно будет работать над этой проблемой. Существует множество причин, почему какой-либо шаблонный код может быть очень медленным для компиляции, в том числе глубоко рекурсивные шаблоны создания экземпляров, излишне причудливые трюки Sfinae, злоупотребление чертами типов и концепциями, а также хороший старомодный сверхрегулярный общий код. Но есть и простые трюки, которые могут решить множество проблем, например, использование неназванных пространств имен (чтобы не тратить впустую время генерации символов для экземпляров, которые действительно не должны быть видимыми за пределами единицы перевода) и специализирующихся типов или концепций типа проверяет шаблоны (в основном, "короткое замыкание" большого количества фантастического метапрограммирования, которое входит в них). Другим потенциальным решением для создания шаблонов является использование extern templates" (из С++ 11) для управления тем, где должны создаваться конкретные экземпляры шаблонов (например, в отдельной cpp файл) и избегать повторного создания его везде, где он использовался.

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

  • Вы можете использовать инструмент Templight "(и его вспомогательный Templight-tools" для работы с трассировками "). Это снова инструмент, основанный на Clang, который можно использовать в качестве замены для компилятора Clang (инструмент на самом деле является инструментальным полномасштабным компилятором), и он будет генерировать полный профиль все экземпляры шаблонов, которые происходят во время компиляции, включая время, затрачиваемое на каждую (и, возможно, оценку потребления памяти, хотя это повлияет на временные значения). Трассировки могут быть впоследствии преобразованы в формат Callgrind и визуализироваться в KCacheGrind, просто прочитайте это описание на странице templight-tools. Это можно в принципе использовать как типичный профайлер во время выполнения, но для профилирования потребления времени и памяти при компиляции шаблонов, тяжелый код.
  • Более элементарный способ поиска худших нарушителей - создание тестовых исходных файлов, которые создают определенные шаблоны, которые, как вы подозреваете, несут ответственность за длительные сроки компиляции. Затем вы компилируете эти файлы, время и пытаетесь работать по-своему (возможно, в режиме "двоичного поиска" ) по отношению к худшим нарушителям.

Но даже с этими трюками выявление узких мест в создании шаблонов проще, чем их решение. Так что, удачи с этим.

Ответ 2

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

  • Тестирование шаблонов может увеличить время компиляции, особенно если сложные шаблоны создаются для нескольких разных типов/параметров в каждом из нескольких исходных файлов. Схемы для явного создания экземпляра шаблона (то есть, убедитесь, что шаблоны создаются только в нескольких исходных файлах, а не во всех) могут сократить время компиляции в таких обстоятельствах (а также время ссылки и размер исполняемого файла). Вам нужно прочитать документацию компилятора, чтобы узнать, как это сделать - это не обязательно происходит по умолчанию и может означать реструктуризацию вашего кода для его поддержки.
  • Заголовочные файлы #include d во многих исходных файлах, независимо от того, нужны они или нет, имеют тенденцию увеличивать время компиляции. Я видел один случай, когда член команды написал "globals.h", что #include d все, а #include d, что везде - и время сборки (в большом проекте) были увеличены на порядок. Это двойной whammy - время компиляции каждого исходного файла увеличивается, и это умножается на количество исходных файлов, прямо или косвенно #include на этот заголовок. Если включение таких функций, как "прекомпилированные заголовки", приводит к ускорению времени сборки для второй и последующих сборок, это, вероятно, вкладчик. (Вы можете рассматривать прекомпилированные заголовки как решение этого, но имейте в виду, что есть и другие компромиссы с их использованием).
  • Если вы используете внешние библиотеки, убедитесь, что они установленных и настроенных локально. Процесс компиляции, который тихо смотрит в Интернете на какой-то компонент (например, жестко закодированное имя файла заголовка, которое находится на каком-то удаленном сервере) будет замедляться вещи значительно. Вы будете удивлены, как часто это происходит с сторонними библиотеками.

Кроме того, методы поиска проблемы зависят от того, как структурирован ваш процесс сборки.

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

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

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

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