Ответ 1
В основном есть две вещи, которые вызывают длительные периоды компиляции: слишком много включает в себя и слишком много шаблонов.
Когда вы включаете слишком много заголовков и что эти заголовки содержат слишком много собственных заголовков, это просто означает, что у компилятора есть много работы, чтобы загрузить все эти файлы, и он потратит чрезмерное количество времени на обработке проходит, что он должен делать на весь код, независимо от того, действительно ли он используется или нет, например, перед обработкой, лексическим анализом, зданием АСТ и т.д. Это может быть особенно проблематичным, когда код распространяется на большое количество потому что производительность очень привязана к вводу/выводу (много времени тратилось только на извлечение и чтение файлов с жесткого диска). К сожалению, библиотеки Boost, как правило, очень структурированы таким образом.
Вот несколько способов или инструментов для решения этой проблемы:
- Вы можете использовать инструмент include-what-you-use. Это инструмент анализа на основе Clang, который в основном рассматривает то, что вы на самом деле используете в своем коде, и из чего они берутся, и затем сообщает о любых возможных оптимизациях, которые вы могли бы сделать, удалив некоторые ненужные включения, используя вместо этого forward-declarations, или, возможно, заменить более широкие "все-в-одном" заголовки более мелкозернистыми заголовками.
- В большинстве компиляторов есть опции для сброса предварительно обработанных источников (в настройках GCC/Clang, it
-E
или-E -P
или просто используемая программа препроцессора GCC Ccpp
). Вы можете взять исходный файл и закомментировать различные включенные операторы или группы включенных операторов и выгрузить предварительно обработанный источник, чтобы увидеть общий объем кода, в который вставляются эти разные заголовки (и, возможно, использовать команду подсчета строк, например$ 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. Это можно в принципе использовать как типичный профайлер во время выполнения, но для профилирования потребления времени и памяти при компиляции шаблонов, тяжелый код.
- Более элементарный способ поиска худших нарушителей - создание тестовых исходных файлов, которые создают определенные шаблоны, которые, как вы подозреваете, несут ответственность за длительные сроки компиляции. Затем вы компилируете эти файлы, время и пытаетесь работать по-своему (возможно, в режиме "двоичного поиска" ) по отношению к худшим нарушителям.
Но даже с этими трюками выявление узких мест в создании шаблонов проще, чем их решение. Так что, удачи с этим.