Які методи можна використовувати для прискорення часу компіляції на C ++?


249

Які методи можна використовувати для прискорення часу компіляції на C ++?

Це питання з'явилося в деяких коментарях до питання стилю програмування C ++ щодо Stack Overflow , і мені цікаво почути, які ідеї існують.

Я бачив відповідне запитання: Чому компіляція C ++ займає так довго? , але це не забезпечує багато рішень.


1
Чи можете ви дати нам якийсь контекст? Або ви шукаєте дуже загальні відповіді?
Піролістичний

1
Дуже схоже на це запитання: stackoverflow.com/questions/364240/…
Адам Розенфілд

Загальні відповіді. У мене дійсно велика база коду, написана багатьма людьми. Ідеї, як напасти, було б добре. А також пропозиції щодо швидкого зберігання компіляцій для нещодавно написаного коду були б цікавими.
Скотт Ленгем

Зверніть увагу , що часто відповідна частина часу збірки не використовується компілятором , але по збірки скриптів
Тхі рр

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

Відповіді:


257

Мовні прийоми

Ідіома Пімпла

Погляньте на ідіому Pimpl тут , і тут , також відому як непрозорий вказівник або класи обробки. Це не тільки прискорює компіляцію, але й підвищує безпеку винятків у поєднанні з функцією swap, що не кидає . Ідіома Pimpl дозволяє зменшити залежності між заголовками та зменшити кількість перекомпіляцій, які потрібно зробити.

Передувальні декларації

Де це можливо, використовуйте прямі декларації . Якщо компілятору потрібно лише знати, що SomeIdentifierце структура чи покажчик чи що завгодно, не включайте все визначення, змушуючи компілятора робити більше роботи, ніж потрібно. Це може мати ефект каскаду, роблячи цей шлях повільніше, ніж потрібно.

В введення / виведення потоків особливо відомі для уповільнення збірки. Якщо вони потрібні у файлі заголовка, спробуйте #including <iosfwd>замість <iostream>та #include <iostream>заголовок лише у файлі реалізації. <iosfwd>Тема містить тільки вперед декларації. На жаль, інші стандартні заголовки не мають відповідного заголовка декларацій.

Віддайте перевагу пропускному посиланню, ніж прохідне значення у функціях підписів. Це позбавить від необхідності #include відповідних визначень типу у файлі заголовка, і вам потрібно буде лише передати декларацію типу. Звичайно, віддайте перевагу const посиланням на non-const посилання, щоб уникнути незрозумілих помилок, але це питання для іншого питання.

Умови охорони

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

#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), ви також можете скоротити час зв’язування.

Отримайте швидший комп’ютер

Більше оперативної пам’яті, швидші жорсткі диски (включаючи SSD) та більше процесорів / ядер змінять швидкість компіляції.


11
Попередньо складені заголовки не є ідеальними. Побічним ефектом їх використання є те, що ви отримуєте більше файлів, ніж потрібно (адже кожен блок компіляції використовує один і той же попередньо складений заголовок), що може примушувати повні перекомпіляції частіше, ніж потрібно. Просто щось пам’ятати.
jalf

8
У сучасних компіляторах #ifndef настільки ж швидкий, як і #pragma один раз (до тих пір, поки захист включення знаходиться у верхній частині файлу). Тож немає жодної користі для #pragma один раз з точки зору швидкості компіляції
jalf

7
Навіть якщо у вас тільки VS 2005, а не 2008, ви можете додати / MP комутатор у параметрах компіляції, щоб увімкнути паралельну побудову на рівні .cpp.
macbirdie

6
SSD-диски були надзвичайно дорогими, коли ця відповідь була написана, але сьогодні вони є найкращим вибором при складанні C ++. Ви отримуєте доступ до безлічі невеликих файлів під час компіляції ;. Для цього потрібно багато IOPS, які постачають SSD.
MSalters

14
Віддайте перевагу пропускному посиланню, ніж прохідне значення у функціях підписів. Це позбавить від необхідності #include відповідних визначень типу у файлі заголовка. Це неправильно , вам не потрібно мати повний тип, щоб оголосити функцію, яка передає значення, вам потрібен лише повний тип, щоб реалізувати та використовувати цю функцію , але в більшості випадків (якщо ви не переадресуєте лише дзвінки) це визначення все одно знадобиться.
David Rodríguez - 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 - це засіб на базі Кланг для профілактики часу та споживання пам'яті шаблонів шаблонів та проведення інтерактивних сесій налагодження для отримання інтроспекції процесу інстанції шаблону".

Ви можете встановити 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, в якому ви зможете простежити найбільше часу / пам’яті, що вимагає часу.

Зменшення кількості екземплярів шаблону

Хоча не існує точного рішення щодо зменшення кількості екземплярів шаблонів, є кілька вказівок, які можуть допомогти:

Класи Refactor з більш ніж одним аргументом шаблону

Наприклад, якщо у вас є клас,

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

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

Не шаблонований код Refactor на окремі одиниці перекладу

Використовуючи цю методику, ви можете скласти загальний розділ один раз і зв'язати його згодом з іншими 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 ігнорує явну інстанцію в цей момент і завжди збирає інстанційовані класи у всіх підрозділах компіляції.

Використовуйте побудови єдності

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

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

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

Ви збираєте цей файл лише один раз і, можливо, зменшите загальні екземпляри між трьома файлами. Загалом важко передбачити, чи може покращення бути значним чи ні. Але одним очевидним фактом є те, що ви втратите паралелізм у своїх збірках (ви не можете більше збирати три файли одночасно).

Крім того, якщо якийсь із цих файлів займає багато пам’яті, можливо, у вас фактично не вистачить пам’яті до завершення компіляції. У деяких компіляторах, таких як GCC , це може ICE (Internal Compiler Error) вашого компілятора через відсутність пам'яті. Тому не використовуйте цю техніку, якщо ви не знаєте всіх плюсів і мінусів.

Заздалегідь складені заголовки

Попередньо складені заголовки (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. Ви повинні переконатися, що попередньо складені файли заголовка є стабільними та не зміняться ( ви завжди можете змінити свій файл файлів )
  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 є типовою (загальнодоступною), тобто якщо ви компілюєте вище як -sharedметод спільної бібліотеки ( ), 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, згадані у відповіді ОВ. Я також бачив, як вони називаються об'ємними і майстерними.
idbrii

То як порівняно UB з WPO / LTCG?
paulm

Це потенційно корисно лише для одноразових компіляцій, а не під час розробки, коли ви переходите між редагуванням, побудовою та тестуванням. У сучасному світі чотири ядра є нормою, можливо, через пару років кількість ядер значно більше. Якщо компілятор і лінкер не в змозі використовувати декілька потоків, то список файлів, можливо, можна розділити на <core-count> + Nпідсписи, складені паралельно, де Nє якесь відповідне ціле число (залежно від системної пам’яті та способу використання машини в іншому випадку).
FooF

15

Існує ціла книга на цю тему, яка має назву « Великий масштаб C ++ Software Design» (автор Джон Лакос).

Шаблони попередньо датують книгу, тому до вмісту цієї книги додайте "використання шаблонів теж може зробити компілятор повільнішим".


Книгу часто згадують у таких видах тем, але для мене це було мало інформації. В основному заявляється використовувати якомога більше переадресаційних декларацій та усунути залежність. Це трохи свідчить про очевидний, окрім того, що використання ідіоми 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 на величину швидше, ніж старий (і насправді був включений у бінутили ).GNU gold ld


Гаразд, так. Вибачте, що питання не виникло, коли я шукав.
Скотт Ленгем

вам не треба було шкодувати це було для Visual C ++. Ваше питання, здається, стосується будь-якого компілятора. так що це добре :)
Йоханнес Шауб - ліб

12

Ось декілька:

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

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

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

@MarkKCowan це залежить від безлічі факторів. Різні комп'ютери мають надзвичайно різну пропускну здатність пам'яті. У процесорах високого класу в наші дні для насичення шини пам'яті потрібно декілька ядер. Крім того, є баланс між вводу-виводу та процесором. Деякий код дуже легко зібрати, інший може бути повільним (наприклад, з великою кількістю шаблонів). Моє поточне правило - це -j2 рази кількість фактичних ядер.
Містер Фооз

11

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

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

Останній, ccache, є кеш-компілятором. Він знову виконує препроцесор, а потім перевіряє у внутрішній базі даних (у локальному каталозі), якщо файл препроцесора вже був зібраний з тими ж параметрами компілятора. Якщо це так, він просто вискакує двійкові та виводить з першого запуску компілятора.

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


2
Я не думаю, що distcc вимагає однакових версій бібліотеки на всіх налаштованих вузлах. distcc робить компіляцію лише віддалено, а не зв'язуванням. Він також надсилає попередньо оброблений код по дроту, тому заголовки, доступні у віддаленій системі, не мають значення.
Фріріх Раабе

9

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

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


1
старе Однак золоте. іноді очевидне забувається.
Алькор

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
}

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


Чи це не має значення лише тоді, коли один заголовок включений до кількох одиниць перекладу? Якщо є лише одна одиниця перекладу (як це часто буває, коли використовуються шаблони), це, мабуть, не матиме впливу.
Завжди навчання

1
Якщо є лише одна одиниця перекладу, навіщо турбуватись класти її в заголовок? Чи не було б більше сенсу просто помістити вміст у вихідний файл? Чи не вся суть заголовків у тому, що вона, ймовірно, буде включена більш ніж одним вихідним файлом?
Еван Теран


5

Використовуйте

#pragma once

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


2
Хоча широко підтримується, #pragma один раз є нестандартним. Дивіться en.wikipedia.org/wiki/Pragma_once
ChrisInEdmonton

7
І в наші дні охоронці регулярного включення мають такий же ефект. Поки вони знаходяться у верхній частині файлу, компілятор цілком здатний трактувати їх як #pragma один раз
jalf

5

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

Прочитайте рекурсивні дії, які вважаються шкідливими (PDF) для обговорення цієї теми в середовищі Unix.


4
  • Оновіть комп’ютер

    1. Отримайте чотирьохядерний ядро ​​(або подвійну квадросистему)
    2. Отримайте багато ОЗУ.
    3. Використовуйте привід оперативної пам'яті, щоб різко скоротити затримки вводу / виводу файлів. (Є компанії, які роблять накопичувачі оперативної пам'яті IDE і SATA, які діють як жорсткі диски).
  • Тоді у вас є всі ваші інші типові пропозиції

    1. Використовуйте попередньо складені заголовки, якщо вони доступні.
    2. Зменшіть кількість з'єднань між частинами вашого проекту. Зміна одного заголовкового файлу зазвичай не потребує перекомпіляції всього проекту.

4

У мене було уявлення про використання накопичувача RAM . Виявилося, що для моїх проектів це все-таки не має великої різниці. Але тоді вони ще досить малі. Спробуй це! Мені було б цікаво почути, наскільки це допомогло.


Ага. Чому хтось за це проголосував? Я спробую це завтра.
Скотт Ленгем

1
Я сподіваюсь, що зниження голосу є тим, що воно ніколи не має великої зміни. Якщо у вас є достатня кількість невикористаної оперативної пам’яті, ОС у будь-якому разі розумно використовуватиме її як кеш диска.
MSalters

1
@MSalters - і скільки було б "достатньо"? Я знаю, що це теорія, але з якоїсь - то причини , використовуючи RAMDrive чи на самому справі дасть значний імпульс. Переходьте до фігури ...
Vilx-

1
достатньо, щоб скомпілювати ваш проект і все одно кешувати вхідні та тимчасові файли. Очевидно, що сторона в ГБ буде безпосередньо залежати від розміру вашого проекту. Слід зазначити, що в старих OS'es (зокрема WinXP) кеші файлів були досить ледачими, не залишаючи невикористаною оперативну пам'ять.
MSalters

Безумовно, оперативної пам'яті швидше, якщо файли вже в операційному режимі, а не спочатку цілу купу повільних вводу-виводу, значить, вони в оперативному режимі? (підйом-повтор для файлів, які змінилися - запишіть їх на диск тощо).
paulm

3

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


динамічні зв'язки запобігають багатьом
видам

3

Не про час компіляції, а про час збірки:

  • Використовуйте ccache, якщо вам доведеться перебудувати ті самі файли, коли ви працюєте над своїми збірними файлами

  • Використовуйте ninja-build замість make. Зараз я компілюю проект із ~ 100 вихідними файлами, і все кешується кешем. потрібно 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.