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

По умолчанию int и long math Java молча обертываются при переполнении и потоке. (Целые операции над другими целыми типами выполняются путем первого продвижения операндов до int или long, на JLS 4.2.2 .)

Начиная с Java 8, java.lang.Math предоставляет addExact , subtractExact , multiplyExact , incrementExact , decrementExact и negateExact для обоих и длинных аргументов, которые выполняют именованную операцию, бросая ArithmeticException при переполнении. (Нет никакого метода divideExact - вам нужно будет проверить один специальный случай (MIN_VALUE / -1) самостоятельно.)

Начиная с Java 8, java.lang.Math также предоставляет toIntExact , чтобы отложить длинное до int, выбрасывая ArithmeticException, если значение long не соответствует int. Это может быть полезно, например, вычисляя сумму int с использованием непроверенной длинной математики, затем используя toIntExact, чтобы сбрасывать в int в конце (но будьте осторожны, чтобы не переполнять вашу сумму).

Если вы все еще используете более старую версию Java, Guava предоставляет статические методы IntMath и LongMath для проверки сложения, вычитания, умножения и возведения в степень (бросания на переполнение). Эти классы также предоставляют методы для вычисления факториалов и биномиальных коэффициентов, возвращающих MAX_VALUE при переполнении (что менее удобно проверять). В примитивных классах Guava, SignedBytes, UnsignedBytes, Shorts и Ints, предоставляются методы checkedCast для сужения больших типов (бросание IllegalArgumentException при недопущении / переполнении, not ArithmeticException), as а также методы saturatingCast, которые возвращают MIN_VALUE или MAX_VALUE при переполнении.

504
задан Martin Broadhurst 28 January 2018 в 08:38
поделиться

10 ответов

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

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

Каждая единица компиляции требуют, чтобы были сотни или даже тысячи заголовков (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++ намного более мощным языком, но они также плохо сказываются с точки зрения скорости компиляции.

764
ответ дан mja 28 January 2018 в 08:38
поделиться

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

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

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

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

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

37
ответ дан tangentstorm 28 January 2018 в 08:38
поделиться

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

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

36
ответ дан Peter Mortensen 28 January 2018 в 08:38
поделиться

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

См. также: C++ Часто Подвергаемые сомнению Ответы

15
ответ дан Dave Ray 28 January 2018 в 08:38
поделиться

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

#include "BigClass.h"

class SmallClass
{
   BigClass m_bigClass;
}

Компиляции намного медленнее, чем:

class BigClass;

class SmallClass
{
   BigClass* m_bigClass;
}
7
ответ дан Andy Brice 28 January 2018 в 08:38
поделиться

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

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

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

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

15
ответ дан Peter Mortensen 28 January 2018 в 08:38
поделиться

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

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

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

4
ответ дан Nemanja Trifunovic 28 January 2018 в 08:38
поделиться

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

4
ответ дан T.E.D. 28 January 2018 в 08:38
поделиться

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

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, если кто-то полностью интегрирует цепочку инструментов и реализует предварительно скомпилированные заголовки. Многие коммерческие компиляторы C делают это в некоторой степени.

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

12
ответ дан 22 November 2019 в 22:31
поделиться

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

2
ответ дан 22 November 2019 в 22:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: