Компиляция файла C ++ занимает очень много времени по сравнению с C # и Java. Компиляция файла C ++ занимает значительно больше времени, чем запуск скрипта Python нормального размера. В настоящее время я использую VC ++, но то же самое с любым компилятором. Почему это?
Две причины, по которым я мог придумать, - это загрузка файлов заголовков и запуск препроцессора, но, похоже, это не должно объяснять, почему это занимает так много времени.
c++
performance
compilation
Дэн Гольдштейн
источник
источник
It takes significantly longer to compile a C++ file
- Вы имеете в виду 2 секунды по сравнению с 1 секундой? Конечно, это вдвое больше, но вряд ли существенно. Или вы имеете в виду 10 минут по сравнению с 5 секундами? Пожалуйста, количественно.Ответы:
Некоторые причины
Заголовочные файлы
Каждый отдельный модуль компиляции требует, чтобы (1) загружались и (2) компилировались сотни или даже тысячи заголовков. Каждый из них, как правило, должен быть перекомпилирован для каждого модуля компиляции, потому что препроцессор гарантирует, что результат компиляции заголовка может отличаться для каждого модуля компиляции. (Макрос может быть определен в одном модуле компиляции, который изменяет содержимое заголовка).
Вероятно, это основная причина, так как для каждой единицы компиляции требуется компиляция огромного количества кода, и, кроме того, каждый заголовок должен быть скомпилирован несколько раз (один раз для каждой единицы компиляции, которая его включает).
соединение
После компиляции все объектные файлы должны быть связаны друг с другом. Это в основном монолитный процесс, который не может быть очень хорошо распараллелен и должен обрабатывать весь ваш проект.
анализ
Синтаксис чрезвычайно сложен для синтаксического анализа, сильно зависит от контекста, и его очень сложно устранить. Это занимает много времени.
Шаблоны
В C #
List<T>
это единственный тип, который компилируется, независимо от того, сколько экземпляров List у вас есть в вашей программе. В C ++vector<int>
это совершенно отдельный типvector<float>
, и каждый из них должен быть скомпилирован отдельно.Добавьте к этому, что шаблоны составляют полный «подъязык» на языке Тьюринга, который должен интерпретировать компилятор, и это может быть до смешного сложным. Даже относительно простой шаблон метапрограммирования шаблонов может определять рекурсивные шаблоны, которые создают десятки и десятки экземпляров шаблонов. Шаблоны могут также приводить к чрезвычайно сложным типам с нелепо длинными именами, добавляя много дополнительной работы компоновщику. (Он должен сравнивать множество имен символов, и если эти имена могут вырасти во многие тысячи символов, это может стать довольно дорогим).
И, конечно, они усугубляют проблемы с заголовочными файлами, потому что шаблоны обычно должны определяться в заголовках, что означает, что для каждого модуля компиляции нужно анализировать и компилировать гораздо больше кода. В простом C-коде заголовок обычно содержит только предварительные объявления, но очень мало реального кода. В C ++ практически весь код находится в заголовочных файлах.
оптимизация
C ++ допускает некоторые весьма существенные оптимизации. C # или Java не позволяют полностью исключать классы (они должны быть там для целей отражения), но даже простая метапрограмма шаблона C ++ может легко генерировать десятки или сотни классов, каждый из которых встроен и снова исключен при оптимизации фаза.
Более того, программа на C ++ должна быть полностью оптимизирована компилятором. Программа AC # может полагаться на JIT-компилятор для выполнения дополнительных оптимизаций во время загрузки, C ++ не имеет таких «вторых шансов». То, что генерирует компилятор, так же оптимизировано, как и собирается.
Машина
C ++ компилируется в машинный код, который может быть несколько сложнее, чем использование байт-кода Java или .NET (особенно в случае x86). (Это упомянуто из-за полноты только потому, что это было упомянуто в комментариях и тому подобном. На практике этот шаг вряд ли займет более крошечной доли общего времени компиляции).
Вывод
Большинство из этих факторов разделяются кодом C, который на самом деле компилируется довольно эффективно. Этап разбора намного сложнее в C ++ и может занимать значительно больше времени, но, вероятно, основным нарушителем являются шаблоны. Они полезны и делают C ++ гораздо более мощным языком, но они также берут свое с точки зрения скорости компиляции.
источник
Замедление не обязательно то же самое с любым компилятором.
Я не использовал Delphi или Kylix, но еще во времена MS-DOS программа Turbo Pascal компилировалась почти мгновенно, а эквивалентная программа Turbo C ++ просто сканировала бы.
Двумя основными отличиями были очень сильная модульная система и синтаксис, позволяющий компиляцию за один проход.
Конечно, возможно, что скорость компиляции не была приоритетом для разработчиков компилятора C ++, но в синтаксисе C / C ++ есть некоторые внутренние сложности, которые усложняют процесс обработки. (Я не эксперт по C, но Уолтер Брайт, и после создания различных коммерческих компиляторов C / C ++, он создал язык D. Одно из его изменений состояло в том, чтобы внедрить контекстно-свободную грамматику, чтобы облегчить синтаксический анализ языка .)
Кроме того, вы заметите, что, как правило, файлы Makefile настроены таким образом, что каждый файл компилируется отдельно в C, поэтому, если все 10 исходных файлов используют один и тот же включаемый файл, этот включаемый файл обрабатывается 10 раз.
источник
Парсинг и генерация кода на самом деле довольно быстрые. Настоящая проблема - открытие и закрытие файлов. Помните, что даже с включенной защитой, компилятор по-прежнему должен открывать файл .H и читать каждую строку (а затем игнорировать ее).
Однажды друг (хотя ему было скучно на работе) взял приложение своей компании и поместил все - все исходные файлы и файлы заголовков - в один большой файл. Время компиляции сократилось с 3 часов до 7 минут.
источник
Другая причина - использование препроцессора C для поиска объявлений. Даже с защитой заголовков .h все равно придется анализировать снова и снова, каждый раз, когда они включены. Некоторые компиляторы поддерживают предварительно скомпилированные заголовки, которые могут помочь с этим, но они не всегда используются.
Смотрите также: C ++ часто задаваемые вопросы
источник
C ++ компилируется в машинный код. Таким образом, у вас есть препроцессор, компилятор, оптимизатор и, наконец, ассемблер, все из которых должны работать.
Java и C # компилируются в байт-код / IL, а виртуальная машина Java / .NET Framework выполняется (или JIT компилируется в машинный код) перед выполнением.
Python - это интерпретируемый язык, который также компилируется в байт-код.
Я уверен, что для этого есть и другие причины, но в целом отсутствие необходимости компилировать на родном машинном языке экономит время.
источник
Самые большие проблемы:
1) Бесконечный повторный заголовок. Уже упоминалось. Смягчения (например, #pragma один раз) обычно работают только на единицу компиляции, а не на сборку.
2) тот факт, что цепочка инструментов часто разделяется на несколько двоичных файлов (make, препроцессор, компилятор, ассемблер, архиватор, impdef, linker и 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, если кто-то полностью интегрирует цепочку инструментов и реализует предварительно скомпилированные заголовки. Многие коммерческие компиляторы Си делают это в некоторой степени.
Обратите внимание, что Unix-модель разделения всего на отдельный двоичный файл является своего рода худшей моделью для Windows (с ее медленным созданием процесса). Это очень заметно при сравнении времени сборки GCC между Windows и * nix, особенно если система make / configure также вызывает некоторые программы только для получения информации.
источник
Сборка 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 ++, работают медленно. Однако есть более быстрые инструменты и способы смягчения медленных частей языка.
Их использование требует небольшого количества смазки для локтя, но преимущества неоспоримы. Более быстрое время сборки ведет к более счастливым разработчикам, большей гибкости и, в конечном итоге, к лучшему коду.
источник
Скомпилированный язык всегда будет требовать больших начальных затрат, чем интерпретируемый язык. Кроме того, возможно, вы не очень хорошо структурировали свой код C ++. Например:
Компилируется намного медленнее, чем:
источник
Простой способ сократить время компиляции в больших проектах C ++ - сделать включаемый файл * .cpp, который включает все файлы cpp в вашем проекте, и скомпилировать его. Это уменьшает проблему взрыва заголовка до одного раза. Преимущество этого заключается в том, что ошибки компиляции будут по-прежнему ссылаться на правильный файл.
Например, предположим, что у вас есть a.cpp, b.cpp и c.cpp .. создайте файл: everything.cpp:
Затем скомпилируйте проект, просто сделав everything.cpp
источник
Некоторые причины:
1) C ++ грамматика является более сложной, чем C # или Java, и занимает больше времени для анализа.
2) (более важно) компилятор C ++ создает машинный код и выполняет все оптимизации во время компиляции. C # и Java идут на полпути и оставляют эти шаги JIT.
источник
Компромисс, который вы получаете, заключается в том, что программа работает чуть быстрее. Это может быть холодным утешением для вас во время разработки, но это может иметь большое значение после завершения разработки и запуска программы пользователями.
источник
Большинство ответов немного неясно, когда упоминается, что C # всегда будет работать медленнее из-за затрат на выполнение действий, которые в C ++ выполняются только один раз во время компиляции, на эту производительность также влияют зависимости времени выполнения (больше вещей для загрузки, чтобы иметь возможность запускать), не говоря уже о том, что программы на C # всегда будут иметь больший объем памяти, и все это приведет к тому, что производительность будет более тесно связана с возможностями доступного оборудования. То же самое относится и к другим языкам, которые интерпретируются или зависят от виртуальной машины.
источник
Я могу подумать о двух проблемах, которые могут повлиять на скорость компиляции ваших программ на C ++.
ВОЗМОЖНЫЙ ВЫПУСК № 1 - СОСТАВЛЕНИЕ ЖАТКИ: (Это может или не может быть уже помощью другого ответа или комментария.) Microsoft Visual C ++ (AKA VC ++) поддерживает предварительно скомпилированные заголовки, которые я настоятельно рекомендую. Когда вы создаете новый проект и выбираете тип программы, которую вы делаете, на экране должно появиться окно мастера установки. Если вы нажмете кнопку «Далее>» в нижней части окна, откроется окно с несколькими списками функций; убедитесь, что флажок рядом с опцией «Precompiled header» установлен. (ПРИМЕЧАНИЕ: это мой опыт работы с консольными приложениями Win32 на C ++, но это может быть не так для всех видов программ на C ++.)
ВОЗМОЖНАЯ ВОПРОС № 2 - МЕСТО, КОТОРОЕ СОБИРАЕТСЯ: Этим летом я прошел курс программирования, и нам пришлось хранить все наши проекты на флэш-накопителях 8 ГБ, поскольку компьютеры в лаборатории, которую мы использовали, стирались каждую ночь в полночь, которая бы стерла всю нашу работу. Если вы компилируете на внешнее устройство хранения данных для переносимости / безопасности / и т. Д., Это может занять очень много времени. время (даже с предварительно скомпилированными заголовками, о которых я упоминал выше) для вашей программы для компиляции, особенно если это довольно большая программа. Мой совет для вас в этом случае будет состоять в том, чтобы создавать и компилировать программы на жестком диске компьютера, который вы используете, и всякий раз, когда вы захотите или по какой-либо причине прекратите работу над вашими проектами, перенесите их на ваш внешний устройства хранения, а затем щелкните значок «Безопасное извлечение устройства и извлечения носителя», который должен появиться в виде небольшой флэш-накопителя за небольшим зеленым кружком с белой галочкой на нем, чтобы отключить его.
Я надеюсь, это поможет вам; дайте мне знать, если это так! :)
источник