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

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

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

Ответ 1

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

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

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

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

соединение

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

анализ

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

Шаблоны

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

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

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

оптимизация

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

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

Машина

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

Заключение

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

Ответ 2

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

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

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

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

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

Ответ 3

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

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

Ответ 4

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

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

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

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

Ответ 5

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

См. также: Часто задаваемые ответы С++

Ответ 6

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

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

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

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

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

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

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

Ответ 7

Building C/С++: что действительно происходит и почему так долго

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

  • Конфигурация
  • Сборка встроенного инструмента
  • Проверка зависимостей
  • Подборка
  • Связь

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

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

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

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

Сборка встроенного инструмента

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

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

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

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

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

Компиляция

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

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

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

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

Ответ 8

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

#include "BigClass.h"

class SmallClass
{
   BigClass m_bigClass;
}

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

class BigClass;

class SmallClass
{
   BigClass* m_bigClass;
}

Ответ 9

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

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

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

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

Ответ 10

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

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

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

Ответ 11

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

Ответ 12

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

Ответ 13

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

ВОЗМОЖНЫЙ ВЫПУСК № 1 - СОСТАВЛЕНИЕ ГОЛОВЫ: (Это может быть или не быть уже рассмотрено другим ответом или комментарием.) Microsoft Visual С++ (AKA VС++) поддерживает предварительно скомпилированные заголовки, которые я очень высоко рекомендовать. Когда вы создаете новый проект и выбираете тип программы, которую вы создаете, на вашем экране должно появиться окно мастера установки. Если вы нажмете кнопку "Далее > " в нижней части окна, окно переместит вас на страницу с несколькими списками функций; убедитесь, что флажок рядом с опцией "Предварительно скомпилированный заголовок" отмечен. (ПРИМЕЧАНИЕ. Это был мой опыт работы с консольными приложениями Win32 на С++, но это может быть не в случае со всеми типами программ на С++.)

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

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

Ответ 14

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

Ответ 15

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