Какие методы можно использовать для ускорения времени компиляции C ++?


249

Какие методы можно использовать для ускорения времени компиляции C ++?

Этот вопрос возник в некоторых комментариях к стилю программирования C ++ в вопросе переполнения стека , и мне интересно узнать, какие есть идеи.

Я видел связанный вопрос, почему компиляция C ++ занимает так много времени? , но это не дает много решений.


1
Не могли бы вы дать нам некоторый контекст? Или вы ищете очень общие ответы?
пиролистический

1
Очень похоже на этот вопрос: stackoverflow.com/questions/364240/…
Адам Розенфилд

Общие ответы. У меня действительно большая база кода, написанная многими людьми. Идеи о том, как атаковать, было бы хорошо. Кроме того, были бы интересны предложения по быстрой компиляции для вновь написанного кода.
Скотт Лэнгхэм

Обратите внимание, что часто соответствующая часть времени сборки используется не компилятором, а сценариями сборки
thi gg

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

Ответы:


257

Языковые техники

Пимпл идиом

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

Форвардные декларации

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

В ввода / вывода потоков особенно известны для замедления сборки. Если они вам нужны в заголовочном файле, попробуйте #include <iosfwd>вместо <iostream>#include и <iostream>заголовок только в файле реализации. <iosfwd>Заголовок содержит только вперед декларации. К сожалению, у других стандартных заголовков нет соответствующего заголовка объявлений.

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

Условия охраны

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

#pragma once
#ifndef filename_h
#define filename_h

// Header declarations / definitions

#endif

Используя как прагму, так и ifndef, вы получаете переносимость простого макро-решения, а также оптимизацию скорости компиляции, которую могут выполнять некоторые компиляторы при наличии pragma onceдирективы.

Уменьшить взаимозависимость

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

Опции компилятора

Предварительно скомпилированные заголовки

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

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

ccache - еще одна утилита, которая использует методы кэширования для ускорения работы.

Используйте параллелизм

Многие компиляторы / IDE поддерживают использование нескольких ядер / процессоров для одновременной компиляции. В GNU Make (обычно используется с GCC) используйте -j [N]опцию. В Visual Studio в настройках есть опция, позволяющая строить несколько проектов параллельно. Вы также можете использовать /MPопцию для паралеллизма на уровне файлов вместо просто паралеллизма на уровне проекта.

Другие параллельные утилиты:

Используйте более низкий уровень оптимизации

Чем больше компилятор пытается оптимизировать, тем сложнее он должен работать.

Общие библиотеки

Перемещение менее часто модифицированного кода в библиотеки может сократить время компиляции. Используя общие библиотеки ( .soили .dll), вы также можете сократить время компоновки.

Получите более быстрый компьютер

Больше оперативной памяти, более быстрые жесткие диски (включая твердотельные накопители) и большее количество процессоров / ядер будут влиять на скорость компиляции.


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

8
В современных компиляторах #ifndef работает так же быстро, как и #pragma (до тех пор, пока защита включения находится в верхней части файла). Так что нет никакой пользы для #pragma один раз с точки зрения скорости компиляции
jalf

7
Даже если у вас только VS 2005, а не 2008, вы можете добавить ключ / MP в опциях компиляции, чтобы включить параллельное построение на уровне .cpp.
Macbirdie

6
Когда написали этот ответ, твердотельные накопители были непомерно дороги, но сегодня они являются лучшим выбором при компиляции C ++. Вы получаете доступ к множеству маленьких файлов при компиляции; Это требует много операций ввода-вывода в секунду, которые поставляет SSD.
MSalters

14
Предпочитайте передачу по ссылке передаче по значению в сигнатурах функций. Это устранит необходимость #include соответствующих определений типов в заголовочном файле. Это неправильно , вам не нужно иметь полный тип для объявления функции, которая передается по значению, вам нужен только полный тип для реализации или использования этой функции , но в большинстве случаев (если только вы не переадресовываете звонки) вам все равно понадобится это определение.
Дэвид Родригес - dribeas

43

Я работаю над проектом STAPL, который представляет собой библиотеку C ++ с большим количеством шаблонов. Время от времени мы должны пересматривать все методы, чтобы сократить время компиляции. Здесь я кратко изложил методы, которые мы используем. Некоторые из этих методов уже перечислены выше:

Поиск наиболее трудоемких разделов

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

Метод 1 - сортировка символов по размеру

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

nm --print-size --size-sort --radix=d YOUR_BINARY

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

Метод 2 - сортировка символов по длине

Вы можете запустить обычную nmкоманду и направить ее в ваш любимый скрипт ( AWK , Python и т. Д.), Чтобы отсортировать символы по их длине . Основываясь на нашем опыте, этот метод определяет самые большие проблемы, делая кандидатов лучше, чем метод 1.

Способ 3 - использовать Templight

« Templight - это инструмент, основанный на Clang, который позволяет профилировать время и потребление памяти при создании экземпляров шаблонов и выполнять интерактивные сеансы отладки, чтобы получить интроспекцию процесса создания шаблонов».

Вы можете установить Templight, проверив LLVM и Clang ( инструкции ) и применив к нему патч Templight. Настройка по умолчанию для LLVM и Clang - при отладке и утверждениях, и они могут значительно повлиять на время компиляции. Кажется, что Templight нуждается в обоих, поэтому вы должны использовать настройки по умолчанию. Процесс установки LLVM и Clang должен занять около часа или около того.

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

Убедитесь, что templight++это в вашем ПУТИ. Теперь для компиляции добавьте следующие ключи CXXFLAGSв ваш Makefile или в параметры командной строки:

CXXFLAGS+=-Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system

Или

templight++ -Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system

После завершения компиляции вы получите файлы .trace.memory.pbf и .trace.pbf, созданные в одной папке. Чтобы визуализировать эти следы, вы можете использовать инструменты Templight, которые могут конвертировать их в другие форматы. Следуйте этим инструкциям для установки templight-convert. Мы обычно используем вывод callgrind. Вы также можете использовать вывод GraphViz, если ваш проект небольшой:

$ templight-convert --format callgrind YOUR_BINARY --output YOUR_BINARY.trace

$ templight-convert --format graphviz YOUR_BINARY --output YOUR_BINARY.dot

Сгенерированный файл callgrind может быть открыт с помощью kcachegrind, в котором вы можете отследить наиболее инстанцирование, которое занимает больше времени / памяти.

Сокращение количества шаблонов

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

Рефакторинг классов с более чем одним аргументом шаблона

Например, если у вас есть класс,

template <typename T, typename U>
struct foo { };

и оба из Tи Uмогут иметь 10 различных опций, вы увеличили возможные экземпляры шаблонов этого класса до 100. Один из способов решить эту проблему - абстрагировать общую часть кода в другой класс. Другой метод заключается в использовании инверсии наследования (реверсирование иерархии классов), но перед использованием этого метода убедитесь, что ваши цели проектирования не поставлены под угрозу.

Рефакторинг не шаблонного кода для отдельных единиц перевода

Используя эту технику, вы можете один раз скомпилировать общий раздел и позже связать его с другими вашими TU (единицами перевода).

Использовать внешние шаблоны (начиная с C ++ 11)

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

Например, в:

enum class PossibleChoices = {Option1, Option2, Option3}

template <PossibleChoices pc>
struct foo { };

Мы знаем, что этот класс может иметь три возможных варианта:

template class foo<PossibleChoices::Option1>;
template class foo<PossibleChoices::Option2>;
template class foo<PossibleChoices::Option3>;

Поместите вышесказанное в единицу перевода и используйте ключевое слово extern в заголовочном файле под определением класса:

extern template class foo<PossibleChoices::Option1>;
extern template class foo<PossibleChoices::Option2>;
extern template class foo<PossibleChoices::Option3>;

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

ПРИМЕЧАНИЕ: MPICH2 игнорирует явное создание экземпляров в этой точке и всегда компилирует созданные экземпляры классов во всех единицах компиляции.

Используйте единство

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

В качестве примера, давайте предположим , что у вас есть три файла foo1.cc, foo2.cc, foo3.ccи все они включают в себя tupleот STL . Вы можете создать foo-all.ccчто выглядит так:

#include "foo1.cc"
#include "foo2.cc"
#include "foo3.cc"

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

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

Предварительно скомпилированные заголовки

Предварительно скомпилированные заголовки (PCH) могут сэкономить вам много времени при компиляции, скомпилировав заголовочные файлы в промежуточное представление, распознаваемое компилятором. Чтобы сгенерировать предварительно скомпилированные файлы заголовков, вам нужно только скомпилировать файл заголовка с помощью обычной команды компиляции. Например, на GCC:

$ g++ YOUR_HEADER.hpp

Это создаст YOUR_HEADER.hpp.gch file( .gchэто расширение для файлов PCH в GCC) в той же папке. Это означает, что если вы включите YOUR_HEADER.hppв какой-то другой файл, компилятор будет использовать ваш YOUR_HEADER.hpp.gchвместо YOUR_HEADER.hppтой же папки ранее.

Есть две проблемы с этой техникой:

  1. Вы должны убедиться, что прекомпилированные файлы заголовков стабильны и не будут меняться ( вы всегда можете изменить свой make-файл )
  2. Вы можете включить только один PCH на единицу компиляции (на большинстве компиляторов). Это означает, что если у вас есть несколько заголовочных файлов для предварительной компиляции, вы должны включить их в один файл (например, all-my-headers.hpp). Но это означает, что вы должны включить новый файл во всех местах. К счастью, у GCC есть решение этой проблемы. Используйте -includeи дайте ему новый заголовочный файл. Вы можете разделить запятыми разные файлы, используя эту технику.

Например:

g++ foo.cc -include all-my-headers.hpp

Используйте безымянные или анонимные пространства имен

Безымянные пространства имен (также известные как анонимные пространства имен) могут значительно уменьшить сгенерированные двоичные размеры. Неназванные пространства имен используют внутреннюю связь, то есть символы, сгенерированные в этих пространствах имен, не будут видны другим TU (единицам перевода или компиляции). Компиляторы обычно генерируют уникальные имена для безымянных пространств имен. Это означает, что если у вас есть файл foo.hpp:

namespace {

template <typename T>
struct foo { };
} // Anonymous namespace
using A = foo<int>;

И вы случайно включили этот файл в два TU (два .cc-файла и скомпилировали их отдельно). Два экземпляра шаблона foo не будут одинаковыми. Это нарушает правило единого определения (ODR). По той же причине использование безымянных пространств имен не рекомендуется в заголовочных файлах. Не стесняйтесь использовать их в своих .ccфайлах, чтобы избежать появления символов в ваших двоичных файлах. В некоторых случаях изменение всех внутренних деталей для .ccфайла показало уменьшение сгенерированных двоичных размеров на 10%.

Изменение параметров видимости

В новых компиляторах вы можете выбрать ваши символы, которые будут либо видимыми, либо невидимыми в динамических общих объектах (DSO). В идеале, изменение видимости может улучшить производительность компилятора, оптимизировать время соединения (LTO) и сгенерированные двоичные размеры. Если вы посмотрите на заголовочные файлы STL в GCC, то увидите, что они широко используются. Чтобы включить выбор видимости, вам нужно изменить свой код для каждой функции, для каждого класса, для каждой переменной и, что более важно, для каждого компилятора.

С помощью видимости вы можете скрыть символы, которые вы считаете их закрытыми, от созданных общих объектов. В GCC вы можете управлять видимостью символов, передавая значение по умолчанию или скрытое для -visibilityопции вашего компилятора. В некотором смысле это похоже на безымянное пространство имен, но более сложным и навязчивым способом.

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

__attribute__((visibility("default"))) void  foo1() { }
__attribute__((visibility("hidden")))  void  foo2() { }
__attribute__((visibility("hidden")))  class foo3   { };
void foo4() { }

Видимость по умолчанию в GCC - это default (public), что означает, что если вы скомпилируете вышеупомянутое как -sharedметод shared library ( ), foo2и класс foo3не будет виден в других TU ( foo1и foo4будет виден). Если вы скомпилируете -visibility=hiddenто только foo1будет видно. Даже foo4будет скрыт.

Вы можете прочитать больше о видимости на вики GCC .


33

Я бы порекомендовал эти статьи из «Игр изнутри, инди-дизайна игр и программирования»:

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


17

Одна из техник, которая работала для меня довольно хорошо в прошлом: не компилируйте несколько исходных файлов C ++ независимо, а скорее генерируйте один файл C ++, который включает в себя все остальные файлы, например так:

// myproject_all.cpp
// Automatically generated file - don't edit this by hand!
#include "main.cpp"
#include "mainwindow.cpp"
#include "filterdialog.cpp"
#include "database.cpp"

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

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

Что бы это ни стоило, проект KDE использовал эту же технику с 1999 года для создания оптимизированных двоичных файлов (возможно, для выпуска). Переключатель на сценарий конфигурации сборки был вызван --enable-final. Из археологического интереса я выкопал сообщение, в котором объявили об этой функции: http://lists.kde.org/?l=kde-devel&m=92722836009368&w=2


2
Я не уверен, действительно ли это то же самое, но я полагаю, что включение «Оптимизация всей программы» в VC ++ ( msdn.microsoft.com/en-us/library/0zza0de8%28VS.71%29.aspx ) должно иметь тот же эффект на производительность во время выполнения, что вы предлагаете. Время компиляции, однако, определенно может быть лучше в вашем подходе!
Филипп

1
@Frerich: Вы описываете сборки Unity, упомянутые в ответе OJ. Я также видел их, называемые массовыми сборками и мастер-сборками.
idbrii

Итак, как UB сравнивается с WPO / LTCG?
Пол

Это потенциально полезно только для одноразовых компиляций, а не во время разработки, когда вы переключаетесь между редактированием, сборкой и тестированием. В современном мире четыре ядра - это норма, возможно, через пару лет число ядер значительно больше. Если компилятор и компоновщик не могут использовать несколько потоков, то список файлов может быть <core-count> + Nразбит на подсписки, которые компилируются параллельно, где Nесть какое-то подходящее целое число (в зависимости от системной памяти и того, как машина используется в противном случае).
FooF

15

На эту тему есть целая книга, которая называется « Разработка больших программ на С ++» (написана Джоном Лакосом).

Книга предшествует шаблонам, поэтому к содержанию этой книги добавьте «использование шаблонов также может замедлить работу компилятора».


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

@ gast128 Я думаю, что смысл в том, чтобы использовать идиомы кодирования, которые допускают пошаговую повторную компиляцию, то есть, чтобы при небольшом изменении исходного кода вам не приходилось перекомпилировать все.
ChrisW

15

Я просто сошлюсь на другой мой ответ: как ВЫ сокращаете время компиляции и время компоновки для проектов Visual C ++ (нативный C ++)? , Еще один момент, который я хочу добавить, но который часто вызывает проблемы - это использование предварительно скомпилированных заголовков. Но, пожалуйста, используйте их только для частей, которые почти никогда не меняются (например, заголовки инструментария GUI). В противном случае они будут стоить вам больше времени, чем сэкономят.

Другой вариант, когда вы работаете с GNU make, включить -j<N>опцию:

  -j [N], --jobs[=N]          Allow N jobs at once; infinite jobs with no arg.

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

Но сам линкер может быть многопоточным, и именно это делает ELF линкер. Это оптимизированный многопоточный код C ++, который, как говорят, связывает объектные файлы ELF на порядок быстрее, чем старый (и фактически был включен в binutils ).GNU gold ld


Да, хорошо. Извините, этот вопрос не возник, когда я искал.
Скотт Лэнгхэм

тебе не нужно было жалеть это было для Visual C ++. Ваш вопрос, кажется, для любого компилятора. так что все в порядке :)
Йоханнес Шауб -

12

Вот некоторые:

  • Используйте все ядра процессора, запустив задание с несколькими компиляциями ( make -j2хороший пример).
  • Отключите или уменьшите оптимизацию (например, GCC намного быстрее, -O1чем -O2или -O3).
  • Используйте предварительно скомпилированные заголовки .

12
Кстати, я считаю, что обычно быстрее запускать больше процессов, чем ядер. Например, в четырехъядерной системе я обычно использую -j8, а не -j4. Причина этого заключается в том, что когда один процесс блокируется при вводе-выводе, другой может компилироваться.
Мистер Фуз

@MrFooz: Я протестировал это несколько лет назад, скомпилировав ядро ​​Linux (из памяти RAM) на i7-2700k (4 ядра, 8 потоков, я установил постоянный множитель). Я забыл точную лучший результат, но -j12до примерно -j18значительно быстрее , чем был -j8, так же , как вы предлагаете. Мне интересно, сколько ядер вы можете иметь до того, как пропускная способность памяти станет ограничивающим фактором ...
Марк К Коуэн

@MarkKCowan это зависит от многих факторов. У разных компьютеров разная пропускная способность памяти. В наши дни высокопроизводительные процессоры насыщают шину памяти несколькими ядрами. Кроме того, существует баланс между вводом / выводом и процессором. Некоторый код очень легко компилировать, другой код может быть медленным (например, с большим количеством шаблонов). Мое эмпирическое правило состоит -jиз 2-х кратного количества реальных ядер.
Мистер Фуз

11

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

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

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

Оба могут использоваться одновременно, так что, если ccache не имеет локальной копии, он может отправить его через сеть на другой узел с distcc, или он может просто внедрить решение без дальнейшей обработки.


2
Я не думаю, что distcc требует одинаковых версий библиотеки на всех настроенных узлах. distcc делает компиляцию только удаленно, а не связывает. Он также отправляет предварительно обработанный код по проводам, поэтому заголовки, доступные в удаленной системе, не имеют значения.
Фрерих Раабе

9

Когда я заканчивал колледж, первый настоящий производительный код C ++, который я видел, содержал эти загадочные директивы #ifndef ... #endif между ними, где были определены заголовки. Я спросил парня, который очень наивно писал код об этих всеобъемлющих вещах и познакомился с миром крупномасштабного программирования.

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


1
Старый но золотой. иногда очевидное забыто.
Alcor

1
«включить охранников»
gast128

8

Больше оперативной памяти

Кто-то говорил об ОЗУ в другом ответе. Я сделал это с 80286 и Turbo C ++ (показывает возраст), и результаты были феноменальными. Как была потеря данных при сбое машины.


в DOS у вас не может быть много памяти
phuclv

6

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

Например:

// T.h
class Class2; // Forward declaration

class T {
public:
    void doSomething(Class2 &c2);
private:
    Class2 *m_Class2Ptr;
};

// T.cpp
#include "Class2.h"
void Class2::doSomething(Class2 &c2) {
    // Whatever you want here
}

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


Разве это не имеет значения, только когда один и тот же заголовок включен в несколько единиц перевода? Если есть только одна единица перевода (как это часто бывает при использовании шаблонов), то это, похоже, не окажет никакого влияния.
AlwaysLearning

1
Если есть только одна единица перевода, зачем вставлять ее в заголовок? Разве не имеет смысла просто поместить содержимое в исходный файл? Разве весь смысл заголовков не в том, что он может быть включен более чем в один исходный файл?
Эван Теран


5

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

#pragma once

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


2
Несмотря на широкую поддержку, #pragma не является стандартным. См en.wikipedia.org/wiki/Pragma_once
ChrisInEdmonton

7
И в наши дни штатные охранники имеют такой же эффект. Пока они находятся в верхней части файла, компилятор полностью способен обрабатывать их как #pragma
jalf

5

Просто для полноты: сборка может быть медленной, потому что система сборки глупа, а также потому, что компилятору требуется много времени, чтобы выполнить свою работу.

Прочитайте « Рекурсивный вред для здоровья» (PDF) для обсуждения этой темы в среде Unix.


4
  • Обнови свой компьютер

    1. Получите четырехъядерное ядро ​​(или двухъядерную систему)
    2. Получите много оперативной памяти.
    3. Используйте RAM-диск, чтобы значительно сократить задержки ввода-вывода файлов. (Есть компании, которые делают диски IDE и SATA RAM, которые действуют как жесткие диски).
  • Тогда у вас есть все ваши другие типичные предложения

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

4

У меня была идея об использовании привода RAM . Оказалось, что для моих проектов это не так уж важно. Но тогда они еще довольно маленькие. Попытайся! Мне было бы интересно услышать, насколько это помогло.


Да. Почему кто-то проголосовал против? Я попробую завтра.
Скотт Лэнгхэм

1
Я ожидаю, что отрицательный голос - потому что это никогда не имеет большого значения. Если у вас достаточно неиспользуемой оперативной памяти, ОС все равно будет разумно использовать ее в качестве дискового кэша.
MSalters

1
@MSalters - а сколько будет "достаточно"? Я знаю, что это теория, но по какой - то причине , используя RAMDrive ли на самом деле даст значительный импульс. Пойди
разберись

1
достаточно, чтобы скомпилировать ваш проект и по-прежнему кэшировать входные и временные файлы. Очевидно, что сторона в ГБ будет напрямую зависеть от размера вашего проекта. Следует отметить, что в старых ОС (в частности, в WinXP) файловые кеши были довольно ленивыми, в результате чего ОЗУ не использовалось.
MSalters

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

3

Динамическое связывание (.so) может быть намного быстрее, чем статическое связывание (.a). Особенно, когда у вас медленный сетевой диск. Это потому, что у вас есть весь код в .a файле, который необходимо обработать и записать. Кроме того, гораздо больший исполняемый файл должен быть записан на диск.


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

3

Не о времени компиляции, а о времени сборки:

  • Используйте ccache, если вам нужно пересобрать те же файлы, когда вы работаете с вашими файлами сборки

  • Используйте ниндзя-билд вместо make. В настоящее время я компилирую проект с ~ 100 исходными файлами, и все кэшируется ccache. сделать нужно 5 минут, ниндзя меньше 1.

Вы можете создавать свои файлы ниндзя из cmake с помощью -GNinja.


3

Где ты проводишь время? Вы связаны с процессором? Память связана? Диск связан? Можете ли вы использовать больше ядер? Больше оперативной памяти? Вам нужен RAID? Вы просто хотите повысить эффективность вашей нынешней системы?

Под gcc / g ++ вы смотрели на ccache ? Это может быть полезно, если вы делаете make clean; makeмного.


2

Быстрее жестких дисков.

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



2

Совместное использование сетевых ресурсов резко замедлит вашу сборку, так как задержка поиска высока. Для чего-то вроде Boost, это имело огромное значение для меня, даже если наш сетевой диск довольно быстрый. Время компиляции игрушечной программы Boost сократилось с 1 минуты до 1 секунды, когда я переключился с общего сетевого ресурса на локальный SSD.


2

Если у вас многоядерный процессор, Visual Studio (2005 и более поздние версии), а также GCC поддерживают многопроцессорные компиляции. Это что-то, чтобы включить, если у вас есть оборудование, конечно.


2
@Fellman, посмотрите другие ответы - используйте опцию -j #.
Страж

1

Хотя это и не «техника», я не мог понять, как проекты Win32 с множеством исходных файлов компилируются быстрее, чем мой пустой проект «Hello World». Таким образом, я надеюсь, что это помогает кому-то, как это сделал я.

В Visual Studio одним из вариантов увеличения времени компиляции является добавочное связывание ( / INCREMENTAL ). Это несовместимо с генерацией кода времени компоновки ( / LTCG ), поэтому не забывайте отключать инкрементное связывание при сборке релизов.


1
Отключение генерации кода времени соединения не является хорошим предложением, поскольку это отключает многие оптимизации. Вам нужно включить только /INCREMENTALв режиме отладки
phuclv

1

Начиная с Visual Studio 2017 у вас есть возможность иметь некоторые метрики компилятора о том, что занимает время.

Добавьте эти параметры в C / C ++ -> Командная строка (Дополнительные параметры) в окне свойств проекта: /Bt+ /d2cgsummary /d1reportTime

Вы можете получить больше информации в этом посте .


0

Использование динамической компоновки вместо статической заставит компилятор работать быстрее.

Если вы используете t Cmake, активируйте свойство:

set(BUILD_SHARED_LIBS ON)

Build Release, используя статические ссылки, может стать более оптимизированным.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.