Как идет компиляция так быстро?

я гуглил и ковырялся на веб-сайте Go, но я не могу найти объяснение необычным временам сборки Go. Являются ли они продуктами языковых функций (или их отсутствия), высоко оптимизированным компилятором или чем-то еще? Я не пытаюсь повысить го, мне просто любопытно.

11 ответов


анализ зависимостей.

с перейти FAQ:

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

Это основная причина быстрой компиляции. И это по замыслу.


Я думаю, что это не то, что компиляторы Go являются быстро, это то, что другие компиляторы медленно.

компиляторы C и c++ должны анализировать огромное количество заголовков - например, для компиляции C++ "hello world" требуется компиляция 18k строк кода, что составляет почти половину мегабайта источников!

$ cpp hello.cpp | wc
  18364   40513  433334

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

скорость компиляции зависит от нескольких факторов.

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

сами компиляторы также могут быть оптимизированы. Например, компилятор Turbo Pascal был написан в оптимизированном вручную ассемблере, что в сочетании с языковым дизайном привело к очень быстрому компилятор работает на оборудовании 286-го класса. Я думаю, что даже сейчас современные компиляторы Pascal (например, FreePascal) быстрее, чем компиляторы Go.


эффективность компиляции была главной целью дизайна:

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

язык FAQ довольно интересен в отношении к конкретным языковым особенностям, связанным с синтаксическим анализом:

во-вторых, язык был разработан, чтобы быть легко анализировать и может быть проанализирован без таблицы символов.


существует несколько причин, почему компилятор Go намного быстрее, чем большинство компиляторов C/C++:

  • Верхняя причина: большинство компиляторов C / C++ демонстрируют исключительно плохие проекты (с точки зрения скорости компиляции). Кроме того, с точки зрения скорости компиляции некоторые части экосистемы C/C++ (например, редакторы, в которых программисты пишут свои коды) не разработаны с учетом скорости компиляции.

  • верхний причина: быстрая скорость компиляции была сознательным выбором в компиляторе Go, а также в языке Go

  • компилятор Go имеет более простой оптимизатор, чем компиляторы C/C++

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

  • компилятор Go генерирует низкоуровневый код сборки раньше, и оптимизатор работает на код сборки, в то время как в типичном компиляторе C/C++ оптимизация проходит работу над внутренним представлением исходного кода. Дополнительные накладные расходы в компиляторе C/C++ связаны с тем, что необходимо создать внутреннее представление.

  • окончательное связывание (5l / 6l / 8l) программы Go может быть медленнее, чем связывание программы C/C++, потому что компилятор Go проходит через весь используемый код сборки, и, возможно, он также выполняет другие дополнительные действия, которые Компоновщики C / C++ не делают

  • некоторые компиляторы C / C++ (GCC) генерируют инструкции в текстовой форме (для передачи ассемблеру), в то время как компилятор Go генерирует инструкции в двоичной форме. Для преобразования текста в двоичный файл необходимо выполнить дополнительную работу (но не много).

  • компилятор Go нацелен только на небольшое количество архитектур ЦП, в то время как компилятор GCC нацелен на большое количество ЦП

  • компиляторы которые были разработаны с целью высокой скорости компиляции, такие как Jikes, являются быстрыми. На процессоре 2GHz Jikes может компилировать 20000 + строк кода Java в секунду (и инкрементный режим компиляции еще более эффективен).


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

Go нужно только включить пакеты, которые вы импортируете напрямую (как те уже импортировали какие они need). Это резко контрастирует с C / C++, где одним файлом начинается с заголовков x, которые включают заголовки y и т. д. Итог: компиляция Go занимает линейное время w.r.t к числу импортированные пакеты, где C / C++ занимает экспоненциальное время.


хорошим тестом для эффективности перевода компилятора является самокомпиляция:сколько времени требуется данному компилятору для компиляции? Для C++ это занимает очень много времени (часов?). Для сравнения, компилятор Pascal/Modula-2/Oberon скомпилирует себя менее чем за один секунда на современной машине [1].

Go был вдохновлен этими языками, но некоторые из основных причин этой эффективности включают в себя:

  1. четко определенными синтаксис, который является математически обоснованным, для эффективного сканирования и синтаксического анализа.

  2. типобезопасный и статически скомпилированный язык, который использует отдельные сборник С зависимость и проверка типа на границы модулей, чтобы избежать ненужного повторного чтения заголовочных файлов и повторной компиляции других модулей-в отличие от независимая компиляция, как в C / C++, где такие кросс-модульные проверки не выполняются компилятор (отсюда необходимость перечитывать все эти заголовочные файлы снова и снова, даже для простой однострочной программы "hello world").

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

эти принципы уже были известны и полностью реализованы в 1970-х и 1980-х годах на таких языках, как Mesa, Ada, Modula-2/Oberon и несколько другие, и только сейчас (в 2010-х годах) находят свой путь в современные языки, такие как Go (Google), Swift (Apple), C# (Microsoft) и некоторые другие.

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

  1. во-первых, поставщики программных платформ, таких как Google, Microsoft и Apple, должны начать с поощрения приложение разработчики используют новую методологию компиляции, позволяя для повторного использования существующей кодовой базы. Это то, что Apple теперь пытается сделать с языком программирования Swift, который может сосуществовать с Objective-C (поскольку он использует ту же среду выполнения).

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

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

ссылки:

[1] http://www.inf.ethz.ch/personal/wirth/ProjectOberon/PO.System.pdf, стр. 6: "компилятор компилируется примерно за 3 секунды". Эта цитата для низкой стоимости Xilinx Плата разработки Spartan-3 FPGA работает на тактовой частоте 25 МГц и имеет 1 Мбайт оперативной памяти. От этого можно легко экстраполировать на "менее 1 секунды" для современного процессора, работающего на тактовой частоте значительно выше 1 ГГц и нескольких Гбайт основной памяти (т. е. на несколько порядков мощнее, чем плата Xilinx Spartan-3 FPGA), даже при учете скорости ввода-вывода. Уже в 1990 году, когда Oberon был запущен на процессоре 25MHz NS32X32 с 2-4 Мбайт основной памяти, компилятор скомпилировал себя всего за несколько секунд. Понятие на самом деле ожидание для компилятора, чтобы закончить цикл компиляции был совершенно неизвестен программистам Oberon даже тогда. Для типичных программ всегда потребовалось больше времени, чтобы удалить палец с кнопки мыши, которая вызвала команду компиляции, чем ждать компилятора, чтобы завершить компиляцию только что вызвал. Это было действительно мгновенное удовлетворение, почти нулевое. время ожидания. И качество производимого кода, хотя и не всегда полностью соответствовало лучшим компиляторам, доступным тогда, было замечательно хорошим для большинства задач и вполне приемлемым в целом.


Go был разработан, чтобы быть быстрым, и это видно.

  1. управление зависимостями: нет файла заголовка, вам просто нужно посмотреть на пакеты, которые непосредственно импортируются (не нужно беспокоиться о том, что они импортируют), таким образом, у вас есть линейные зависимости.
  2. грамматика: грамматика языка проста, поэтому легко разбирается. Хотя количество функций уменьшается, таким образом, сам код компилятора является жестким (несколько путей).
  3. перегрузка не допускается: вы видите символ, вы знайте, к какому методу он относится.
  4. тривиально можно компилировать Go параллельно, потому что каждый пакет может быть скомпилирован независимо.

обратите внимание, что GO-не единственный язык с такими функциями (модули являются нормой в современных языках), но они сделали это хорошо.


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

однако замедлить компилятор нетрудно.

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

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


просто (своими словами), потому что синтаксис очень прост ( анализировать и анализировать)

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

например в этом примере кода: "интерфейсы" компилятор не идет и не проверяет, предназначен ли тип реализовать данный интерфейс при анализе этого типа. Только пока ... используется (и если используется ) проверка выполняется.

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

следующее не компилируется:

package main
func main() {
    var a int 
    a = 0
}
notused.go:3: a declared and not used

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

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

еще раз, своими словами.


Я думаю, Go был разработан параллельно с созданием компилятора, поэтому они были лучшими друзьями с рождения. (IMO)


цитата из книги "Язык Программирования Go" Алан Донован и Брайан Керниган:

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