Почему компиляция C++ занимает так много времени?

компиляции файла c++ занимает очень много времени по сравнению с C# и Java. Для компиляции файла c++ требуется значительно больше времени, чем для запуска скрипта Python нормального размера. В настоящее время я использую VC++, но это то же самое с любым компилятором. Почему так?

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

14 ответов


нескольким причинам

заголовочные файлы

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

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

связь

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

анализ

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

Шаблоны

В C#List<T> - Это единственный тип, который компилируется, независимо от того, сколько экземпляров списка у вас есть в вашей программе. В C++, vector<int> является полностью отдельным типом от vector<float>, и каждый из них должен быть составлен отдельно.

добавьте к этому, что шаблоны составляют полный "подязык" Тьюринга, который компилятор должен интерпретировать, и это может стать невероятно сложным. Даже относительно простой код метапрограммирования шаблонов может определять рекурсивные шаблоны, которые создают десятки экземпляров шаблонов. Шаблоны также могут приводить к чрезвычайно сложным типам с смехотворно длинными именами, добавляя много дополнительной работы компоновщику. (Он должен сравнить много имен символов, и если эти имена могут вырасти во многие тысячи персонажи, которые могут стать довольно дорого).

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

оптимизация

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

кроме того, программа на C++ должна быть полностью оптимизирована компилятором. Программа C# может полагаться на JIT компилятор для выполнения дополнительных оптимизаций во время загрузки, C++ не получает таких "вторых шансов". Что компилятор генерирует оптимизированный как это сделать.

машина

C++ компилируется в машинный код, который может быть несколько сложнее, чем байт-код Java или .NET (особенно в случае x86). (Это упоминается из-за полноты только потому, что это было упомянуто в комментариях и тому подобное. На практике, этот шаг вряд ли займет больше крошечная часть общего времени компиляции).

вывод

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


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

Я не использовал Delphi или Kylix, но еще в дни MS-DOS программа Turbo Pascal компилировалась бы почти мгновенно, в то время как эквивалентная программа Turbo c++ просто сканировала бы.

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

вполне возможно, что скорость компиляции просто не была приоритетом для C++ разработчики компиляторов, но есть также некоторые неотъемлемые осложнения в синтаксисе C/C++, которые затрудняют его обработку. (Я не эксперт по C, но Уолтер Брайт, и после создания различных коммерческих компиляторов C/C++, он создал язык D. одно из его изменений должен был обеспечить контекстно-свободную грамматику, чтобы упростить синтаксический анализ языка.)

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


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

друг однажды (когда ему было скучно на работе) взял приложение своей компании и поместил все-все исходные и заголовочные файлы-в один большой файл. Время компиляции сократилось с 3 часов до 7 минут.


на C++ компилируется в машинный код. Таким образом, у вас есть предварительный процессор, компилятор, оптимизатор и, наконец, ассемблер, все из которых должны работать.

Java и C# компилируются в byte-code/IL, а виртуальная машина Java/.NET Framework выполняется (или JIT компилируется в машинный код) до выполнения.

Python-это интерпретируемый язык, что тоже компилируется в байт-код.

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


другой причиной является использование предварительного процессора C для обнаружения объявлений. Даже с охраной заголовка .h все еще нужно анализировать снова и снова, каждый раз, когда они включены. Некоторые компиляторы поддерживают предварительно скомпилированные заголовки, которые могут помочь в этом, но они не всегда используются.

Читайте также: C++ Часто Задаваемые Вопросы Ответы


самые большие проблемы:

1) бесконечное заголовок перепарсинг. Уже упомянутый. Смягчения (например, #pragma once) обычно работают только на единицу компиляции, а не на сборку.

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

см. также это обсуждение на comp.компиляторы:http://compilers.iecc.com/comparch/article/03-11-078 специально этот:

http://compilers.iecc.com/comparch/article/02-07-128

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

обратите внимание, что Unix-модель факторинга всего на отдельный двоичный файл является своего рода наихудшей моделью для Windows (с ее медленным созданием процесса). Это очень заметно при сравнении времени сборки GCC между Windows и *nix, особенно если система make/configure также вызывает некоторые программы только для получения информации.


построение C / C++: что на самом деле происходит и почему это занимает так много времени

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

  • конфигурация
  • инструмент построения startup
  • проверка зависимостей
  • сборник
  • связь

теперь мы рассмотрим каждый шаг более подробно, сосредоточившись на том, как их можно сделать быстрее.

конфигурация

Это первый шаг при запуске сборки. Обычно означает запуск сценария настройки или CMake, Gyp, SCons или какого-либо другого инструмента. Это может занять от одной секунды до нескольких минут для очень больших плагина основе скрипт configure.

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

создать загрузочный инструмент

Это то, что происходит, когда вы запускаете make или нажимаете на значок сборки в IDE (который обычно является псевдонимом для make). Двоичный файл средства сборки запускается и считывает файлы конфигурации а также конфигурация сборки, которая, как правило, одно и то же.

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

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

проверка зависимостей

после того, как инструмент сборки прочитал его конфигурацию, он должен определить, какие файлы изменились и какие из них необходимо перекомпилировать. Файлы конфигурации содержат направленный ациклический граф, описывающий зависимости построения. Этот график обычно построенный во время шага configure. Время запуска инструмента сборки и сканер зависимостей выполняются для каждой сборки. Их объединенная среда выполнения определяет нижнюю границу цикла edit-compile-debug. Для небольших проектов это время обычно несколько секунд или около того. Это терпимо. Есть альтернативы. Самый быстрый из них-Ninja, который был построен инженерами Google для Chromium. Если вы используете CMake или Gyp для сборки, просто переключитесь на их бэкэнды ниндзя. Вам не придется измените что-нибудь в самих файлах сборки, просто наслаждайтесь повышением скорости. Ninja не упакован в большинстве дистрибутивов, хотя, так что вам, возможно, придется установить его самостоятельно.

сборник

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

  • слияние включает в себя
  • парсинг кода
  • генерация кода/оптимизация

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

#include "BigClass.h"

class SmallClass
{
   BigClass m_bigClass;
}

компилируется намного медленнее, чем:

class BigClass;

class SmallClass
{
   BigClass* m_bigClass;
}

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

например, предположим, что у вас есть a.cpp, b.cpp и c.cpp - ... . создайте файл: все.cpp:

#include "a.cpp"
#include "b.cpp"
#include "c.cpp"

затем скомпилируйте проект, просто сделав всё.cpp


некоторые причины:

1) грамматика C++ сложнее, чем C# или Java, и требует больше времени для анализа.

2) (что более важно) компилятор C++ создает машинный код и выполняет все оптимизации во время компиляции. C# и Java идут только на полпути и оставляют эти шаги для JIT.


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


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


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

ВОЗМОЖНАЯ ПРОБЛЕМА #1-КОМПИЛЯЦИЯ ЗАГОЛОВКА: (это может быть или не может быть уже рассмотрено другим ответом или комментарием.) Microsoft Visual C++ (A. K. A. VC++) поддерживает предварительно скомпилированные заголовки, которые я настоятельно рекомендую. При создании нового проекта и выборе типа создаваемой программы на вашем компьютере должно появиться окно Мастера установки экран. Если вы нажмете кнопку " Далее > "внизу, окно приведет вас к странице, которая имеет несколько списков функций; убедитесь, что флажок рядом с опцией" предварительно скомпилированный заголовок " установлен. (Примечание: это был мой опыт работы с консольными приложениями Win32 на C++, но это может быть не так со всеми видами программ на C++.)

ВОЗМОЖНАЯ ПРОБЛЕМА #2-МЕСТОПОЛОЖЕНИЕ КОМПИЛИРУЕТСЯ В: этим летом я прошел курс программирования, и нам пришлось хранить все из наших проектов на флэш-накопителях 8GB, поскольку компьютеры в лаборатории, которую мы использовали, стирались каждую ночь в полночь, что стерло бы всю нашу работу. Если вы компилируете на внешнее запоминающее устройство ради переносимости / безопасности / и т. д., это может занять очень долго время (даже с предварительно скомпилированными заголовками, о которых я упоминал выше) для компиляции вашей программы, особенно если это довольно большая программа. Мой совет для вас в этом случае будет заключаться в создании и компиляции программ на жестком диске компьютера, который вы используете, и всякий раз, когда вы хотите/должны прекратить работу над вашим проектом(ы) по какой-либо причине, перенести их на внешнее запоминающее устройство, а затем нажмите кнопку "безопасно удалить оборудование и извлечь носитель" значок, который должен появиться в виде небольшой флэш-накопитель за небольшой зеленый круг с белой галочкой на нем, чтобы отключить его.

Я надеюсь, что это поможет вам; дайте мне знать, если это так! :)


Как уже отмечалось, компилятор тратит много времени на создание и повторное создание экземпляров шаблонов. До такой степени, что есть проекты, которые фокусируются на этом конкретном элементе и утверждают наблюдаемое ускорение 30x в некоторых действительно благоприятных случаях. См.http://www.zapcc.com.