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

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

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

вопрос задан 25.11.2008
Dan Goldstein
16005 репутация

14 ответов


  • 727 рейтинг

    Несколько причин

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

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

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

    Связывание

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

    Разбор

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

    Шаблоны

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

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

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

    Оптимизация

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

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

    Машина

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

    Заключение

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

    ответ дан jalf, с репутацией 203342, 25.11.2008
  • 36 рейтинг

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

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

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

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

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

    ответ дан tangentstorm, с репутацией 5194, 25.11.2008
  • 32 рейтинг

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

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

    ответ дан James Curran, с репутацией 83920, 25.11.2008
  • 16 рейтинг

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

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

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

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

    ответ дан Alan, с репутацией 31783, 25.11.2008
  • 15 рейтинг

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

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

    ответ дан Dave Ray, с репутацией 33497, 25.11.2008
  • 11 рейтинг

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

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

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

    См. Также это обсуждение комп. составители: http: // compilers. МКСЭП. com / comparch / article / 03-11-078 специально этот:

    http: // compilers. МКСЭП. com / comparch / article / 02-07-128

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

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

    ответ дан Marco van de Voort, с репутацией 21802, 2.05.2009
  • 9 рейтинг

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

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

    • Конфигурация
    • Запуск инструмента сборки
    • Проверка зависимостей
    • Сборник
    • Ссылка

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

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

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

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

    Запуск средства сборки

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

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

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

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

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

    Подборка

    На этом этапе мы наконец-то вызываем компилятор. Обрезая некоторые углы, вот примерные шаги.

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

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

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

    ответ дан Ravindra Acharya, с репутацией 142, 23.04.2015
  • 7 рейтинг

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

    #include "BigClass.h"
    
    class SmallClass
    {
       BigClass m_bigClass;
    }
    

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

    class BigClass;
    
    class SmallClass
    {
       BigClass* m_bigClass;
    }
    
    ответ дан Andy Brice, с репутацией 2001, 25.11.2008
  • 5 рейтинг

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

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

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

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

    ответ дан rileyberton, с репутацией 307, 3.03.2013
  • 4 рейтинг

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

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

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

    ответ дан Nemanja Trifunovic, с репутацией 22115, 25.11.2008
  • 4 рейтинг

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

    ответ дан T.E.D., с репутацией 35179, 31.12.2008
  • 2 рейтинг

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

    ответ дан Panic, с репутацией 202, 20.06.2009
  • 1 рейтинг

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

    ВОЗМОЖНАЯ ПРОБЛЕМА № 1 - СОСТАВЛЕНИЕ ЗАГОЛОВОКА: (Это может быть, а может и не быть уже решено другим ответом или комментарием. ) Microsoft Visual C ++ (A. K. A. VC ++) поддерживает предварительно скомпилированные заголовки, которые я настоятельно рекомендую. Когда вы создаете новый проект и выбираете тип программы, которую вы делаете, на экране должно появиться окно мастера установки. Если вы нажмете кнопку «Далее & gt;» в нижней части окна, откроется окно с несколькими списками функций; убедитесь, что флажок рядом с опцией «Precompiled header» установлен. (ПРИМЕЧАНИЕ: это мой опыт работы с консольными приложениями Win32 на C ++, но это может быть не так для всех видов программ на C ++. )

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

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

    ответ дан cjor530, с репутацией 10, 18.08.2016
  • 0 рейтинг

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

    ответ дан akim, с репутацией 4202, 26.05.2015