Каковы ключевые варианты дизайна для создания невероятно быстрого компилятора?

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

Во-первых, позвольте мне предупредить некоторые очевидные недоразумения моего вопроса:

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

  2. Меня также не интересует обсуждение того, почему компиляторы C++ обычно медленнее, чем компиляторы Java (например). Мне интересно, какие методы можно использовать для ускорения компилятора для любого данного языка.

  3. Я также не хочу слышать о распределенных системах компиляции, таких как Incredibuild от Microsoft или distcc от Unix. Эти системы не дают вам более быстрые компиляторы, они просто дают вам больше компиляторов. Что, безусловно, полезно, но я задаю не этот вопрос. Я хочу знать, как разработать быстрый компилятор для одного процессора.

  4. И ccache не тот ответ, который я ищу. Это система, которая позволяет вообще не использовать компилятор, но не делает его быстрее. Опять же, это полезно; опять же, это не тот вопрос, который я задаю.

Надеюсь, мой вопрос теперь кристально ясен. Но, возможно, немного истории сделает это еще яснее.

Компиляторы C раньше были очень медленными. Затем, в 1986 году, компания THINK Technologies представила Lightspeed C для Macintosh, и программы компилировались почти мгновенно. Lightspeed C был настолько намного быстрее, чем все остальные компиляторы C, что сравнивать их было практически невозможно. (Возможно, Lightspeed C не был первым из нового поколения молниеносных компиляторов, но он был первым в моем опыте. Turbo Pascal появился раньше [1983], но у меня не было опыта работы с ним, поэтому я не знаю, как это сделать. это сравнимо, по скорости.)

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

Ответ может быть таким простым: с такими IDE, как Lightspeed и Turbo, встроенный редактор уже имеет исходный код в оперативной памяти. Если компилятор работает с этими данными, он исключает дисковый ввод-вывод, который является самой медленной частью любого компилятора. Вероятно, это очень важный фактор повышения скорости, если размер исходного кода невелик по сравнению с объемом памяти. (В те дни размеры оперативной памяти были намного меньше, но тогда такими же были типичные размеры программ.)

Это оно? Или были задействованы другие важные инновации? И были ли с тех пор важные улучшения в скорости компилятора?


person Thom Boyer    schedule 13.09.2010    source источник
comment
+1 за правильное использование злого в качестве наречия.   -  person Jon Purdy    schedule 02.10.2010


Ответы (6)


  • Простой синтаксис, который можно проанализировать за один проход.
  • Простой целевой код. Если вы не нацеливаете машинный код напрямую, вы можете избежать многих неприятностей.
  • Не компилируется вообще. Если вам не нужно быстрое выполнение или дизайн в основном для одноразовых сценариев, вам не нужно тратить время на анализ кода.
  • Не надо, повторяю, не пытаться перехитрить управление дисками/кэшем вашей ОС. Mmap весь проклятый файл и прочитайте его, как если бы вы читали его из ОЗУ. Если у вас нет виртуальной памяти, быстрая компиляция — это наименьшая из ваших забот.
  • Избегайте создания XML DOM, таких как раздутые структуры данных для AST. Вам не нужно анимировать приоритеты операторов. Сохраняйте указатели на mmaped данные вместо того, чтобы копировать их.
  • Профилируйте свой код, если хотите быстро. Всегда.

Добавление:

  • Изучите различные способы разбора. Если вы не очень уверены в своих навыках написания парсеров, используйте проверенные инструменты генератора парсеров/лексеров, такие как antlr, Lemon и т. д.
person artificialidiot    schedule 13.09.2010
comment
Пожалуйста, прочитайте вопрос полностью. Большая часть вашего ответа относится к категориям, которые я явно назвал не относящимися к делу. - person Thom Boyer; 15.09.2010
comment
Нет специального ингредиента, который заставил бы компилятор или любую другую часть программного обеспечения работать быстрее на порядок. Чистый код, профилирование, отсутствие глупостей — вот и все. - person artificialidiot; 15.09.2010
comment
Правильный выбор дизайна на верхнем уровне — например, выбор правильных структур данных и алгоритмов — может легко заставить вашу программу работать на порядок быстрее, чем другую. Очистка вашего кода и профилирование обычно приводят к небольшим улучшениям, условно говоря. Я считаю, что ускорение компилятора, которое я наблюдал в 80-х, было вызвано различными вариантами дизайна, а не просто ускорением отдельных частей. Это те варианты дизайна, которые я пытаюсь понять. - person Thom Boyer; 21.09.2010
comment
Кстати, ваше замечание по поводу использования mmap очень хорошее. Ускорение моего кода файлового ввода-вывода мало что изменит по сравнению с результатами этого дизайнерского решения. - person Thom Boyer; 21.09.2010
comment
Если вы позволите мне предположить, упомянутое вами ускорение больше связано с увеличением доступности оперативной памяти, поэтому компилятор может избежать записи промежуточных больших структур данных в магнитную память. Хорошая структура данных, разработанная для медленного последовательного доступа, чаще всего будет работать очень плохо для более быстрого произвольного доступа. Большинство старых языков либо предназначены для очень легкого анализа, либо компилируются на мейнфреймах в пакетных заданиях. - person artificialidiot; 21.09.2010

Одна проблема заключается в том, что вы выдаете для сгенерированного кода. Вы можете потратить столько времени компилятора на оптимизацию, сколько захотите. Прямолинейная генерация, возможно, даже выглядящая глупо, сэкономит вам время. Конечно, когда я использовал Turbo Pascal и Lightspeed C, самым важным было получение исполняемого файла, а не его оптимизация. В то время технология компилятора для настольных компьютеров серьезно отставала от технологии компиляции для мейнфреймов.

Еще одной особенностью Turbo Pascal и Lightspeed C была интеграция. Это было здорово, особенно в те дни, когда не было многозадачных домашних компьютеров. В отличие от первого моего компилятора C (для CP/M), мне не нужно было вносить изменения в редакторе, закрывать его, выполнять компиляцию, компоновку и выполнение. Возможно, это было частью того, что вы видели: быстрое выполнение компонентов без необходимости вводить сложные команды. Я могу повторить это сейчас, запустив несколько терминалов на рабочем столе Gnome: один для vim, один для запуска gcc и один для выполнения.

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

person David Thornley    schedule 15.09.2010
comment
Еще одним фактором, способствовавшим высокой скорости Turbo Pascal (оригинала), было то, что он был написан вручную на ассемблере. - person NealB; 15.09.2010

Здравый смысл заключается в том, что парсеры на основе рекурсивного спуска сверху вниз работают быстрее, чем парсеры на основе правил LALR(k), такие как созданные yacc, при условии, что они хорошо закодированы. Парсеры, закодированные вручную, также могут в некоторых случаях выдавать более качественные сообщения об ошибках.

OTOH, хорошей причиной для использования чего-то вроде yacc является то, что LALR (1) может однозначно анализировать более широкий класс языков, чем рекурсивный спуск, что эквивалентно классу языков LL (1), если я правильно помню. Кроме того, на создание и проверку парсера в стиле yacc может уйти меньше времени, чем на созданный вручную.

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

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

person Alex Blakemore    schedule 17.09.2010

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

Теперь я не могу комментировать Lightspeed (поскольку я ничего о нем не знаю), но в случае Lattice & Microsoft C (медленный) против Borland TurboC (быстрый) Borland хранил все файлы в памяти и компилировал их там (если ваша программа потерпела крах, это может вывести IDE из строя, и вы потеряете несохраненный исходный код!). IDE от Micrsoft всегда сохраняла файлы на диск, а затем запускала отдельную программу для чтения диска и его компиляции.

Использование заголовочных файлов прекомпилятора также помогло ускорить компиляцию C/C++.

Еще одна вещь, которая помогает ускорить компиляцию, — это язык, предназначенный для однопроходной компиляции. Например, Pascal требует, чтобы каждая используемая функция была определена (а не просто объявлена, как в C++) перед ее использованием (поэтому основная функция должна быть последней в исходном файле).

person James Curran    schedule 13.09.2010
comment
Пожалуйста, прочитайте вопрос полностью. Большая часть вашего ответа относится к категориям, которые я явно назвал не относящимися к делу. Пункт о файлах в памяти - это просто повторение части моего вопроса. - person Thom Boyer; 15.09.2010
comment
Обратите внимание, что компиляторы C++ также медленнее, потому что синтаксический анализ C++ сложнее. Я не знаю, почему C был спроектирован таким образом, чтобы его было трудно анализировать, но, вероятно, это было непреднамеренно. C++ не мог уйти от этого, в то время как Java и C# могли. - person David Thornley; 15.09.2010

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

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

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

person High Performance Mark    schedule 15.09.2010
comment
Да, современный компилятор должен использовать доступные ему ядра, включая любые графические процессоры. Мне нравится ваша идея конвейеризации этапов компиляции. Это хорошие идеи, и я благодарю вас за то, что поделились ими. Разве это исключено моими «правилами», как вы выразились? Да! Это хороший ответ на вопрос, которого я не задавал. Суть моего вопроса в следующем: что произошло в 1980-х, что сделало компиляторы намного быстрее? Я должен был сделать это заголовком вопроса. Современные компиляторы используют идеи, которые вы все упомянули; они также используют методы, изобретенные в 1980-х, и это часть картины, которую я упускаю. - person Thom Boyer; 21.09.2010

Я думаю, что одним из больших изменений в Turbo Pascal было то, что во многих предыдущих компиляторах/ассемблерах/компоновщиках исходный код и объектный код находились на диске, как и части компилятора/ассемблера/компоновщика. Попытка чтения и записи нескольких файлов одновременно на одном диске часто более чем в два раза медленнее, чем чтение или запись одного файла. Turbo Pascal хранил всю систему разработки в оперативной памяти, и во многих случаях исходный или объектный код также находился в оперативной памяти.

В конце жизни Commodore 64 появился ассемблер под названием Fast Assembler, который исправлял базовый интерпретатор, добавляя коды операций на языке ассемблера и несколько новых директив. Директива ORG устанавливает целевое расположение кода и флаг «проход». Если бы флаг «pass» был снят, каждый код операции увеличивал бы местоположение целевого кода на размер инструкции, но не генерировал бы никакого кода и не жаловался бы на переходы вне допустимого диапазона. Если установлен флаг прохода, будет сгенерирован код. Чтобы собрать программу, нужно окружить ее циклом for/next, который нужно пройти три раза, с установленным флагом «pass» в последний раз. Поскольку все хранилось в оперативной памяти, цикл редактирования-сборки-тестирования был чрезвычайно быстрым по сравнению с любыми более ранними ассемблерами.

person supercat    schedule 02.10.2010