как минимизировать время компиляции языка программирования? - PullRequest
7 голосов
/ 23 февраля 2009

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

Ответы [ 17 ]

10 голосов
/ 23 февраля 2009

Ваша главная проблема сегодня - ввод / вывод. Ваш процессор во много раз быстрее основной памяти, а память примерно в 1000 раз быстрее, чем доступ к жесткому диску.

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

Попробуйте эти правила:

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

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

  2. Попробуйте разрешить компилировать файлы самостоятельно. Например, создайте «отсутствующий пул символов» для проекта. Отсутствующие символы не должны вызывать сбои компиляции как таковые. Если вы найдете где-нибудь пропущенный символ, удалите его из пула. Когда все файлы скомпилированы, убедитесь, что пул пуст.

  3. Создать кеш с важной информацией. Например: Файл X использует символы из файла Y. Таким образом, вы можете пропустить компиляцию файла Z (который ничего не ссылается на Y), когда Y изменяется. Если вы хотите пойти еще дальше, поместите все символы, которые определены в любом месте пула. Если файл изменяется таким образом, что символы добавляются / удаляются, вы сразу узнаете, какие файлы затронуты (даже не открывая их).

  4. Компиляция в фоновом режиме. Запустите процесс компиляции, который проверяет каталог проекта на наличие изменений и компилирует их, как только пользователь сохранит файл. Таким образом, вам нужно будет компилировать только несколько файлов каждый раз вместо всего. В долгосрочной перспективе вы будете компилировать намного больше, но для пользователя время оборота будет намного короче (= время, которое пользователь должен ждать, пока он сможет запустить скомпилированный результат после изменения).

  5. Используйте компилятор «Just in time» (т.е. компилируйте файл, когда он используется, например, в операторе импорта). Затем проекты распространяются в исходной форме и компилируются при первом запуске. Python делает это. Для этого вы можете предварительно скомпилировать библиотеку во время установки вашего компилятора.

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

3 голосов
/ 23 февраля 2009

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

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

Таким образом, оказывается, что самое лучшее, что вы можете сделать, чтобы ускорить компилятор, - это прочитать весь файл в память за один большой ввод-вывод, выполнить все ваши лексические операции, синтаксический анализ и т. Д. Из ОЗУ, а затем записать вывести результат на диск одним большим вводом / выводом.

Я говорил об этом с одним из главных парней, поддерживающих Gnat (компилятор Ada GCC), и он сказал мне, что на самом деле он помещал все, что мог, на RAM-диски, так что даже его файловый ввод-вывод действительно был считыванием RAM и пишет.

3 голосов
/ 23 февраля 2009

Как можно уменьшить время компиляции?

  • Нет компиляции (перевод на языке)
  • отложенная (как раз вовремя) компиляция
  • Инкрементная компиляция
  • Предварительно скомпилированные заголовочные файлы
2 голосов
/ 24 февраля 2009

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

  • Напишите двухпроходный компилятор: символы для IR, IR для кода. (Проще написать трех -проходный компилятор, который будет проходить символы -> AST -> IR -> код, но это не так быстро.)

  • Как следствие, нет оптимизатора; сложно написать быстрый оптимизатор.

  • Рассмотрите возможность создания байт-кода вместо собственного машинного кода. Виртуальная машина для Lua является хорошей моделью.

  • Попробуйте распределитель регистров линейного сканирования или простой распределитель регистров, которые Фрейзер и Хансон использовали в lcc .

  • В простом компиляторе лексический анализ часто является самым узким местом производительности. Если вы пишете код на C или C ++, используйте re2c . Если вы используете другой язык (который вы найдете гораздо более приятным), прочитайте статью о re2c и примените полученные уроки.

  • Генерируйте код, используя максимальный munch, или, возможно, iburg .

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

  • Как отмечалось выше, создавайте свой язык так, чтобы не было ничего похожего на #include. Либо не используйте файлы интерфейса, либо прекомпилируйте файлы интерфейса. Эта тактика резко уменьшает берндерн на лексере, который, как я уже говорил, часто является самым большим узким местом.

2 голосов
/ 23 февраля 2009

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

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

C ++ страдает как - если вы не используете идиому pImpl - он требует подробностей реализации каждого объекта и всех встроенных функций для компиляции клиентского кода.

Java (от источника к байт-коду) страдает, потому что грамматика не различает объекты и классы - вы должны загрузить класс Foo, чтобы увидеть, является ли Foo.Bar.Baz полем Baz для объекта, на который ссылается статическое поле Bar Класс Foo или статическое поле класса Foo.Bar. Вы можете внести изменения в исходный код класса Foo между ними, и не изменять исходный код клиента, но все равно придется перекомпилировать клиентский код, поскольку байт-код различает две формы, даже если синтаксис не , Байт-код AFAIK Python не различает два - модули являются истинными членами своих родителей.

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

C страдает, если вы часто используете препроцессор, но фактическая компиляция происходит быстро; большая часть кода C использует typedef struct _X* X_ptr, чтобы скрыть реализацию лучше, чем C ++ - заголовок C может легко состоять из typedefs и объявлений функций, обеспечивая лучшую инкапсуляцию.

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

1 голос
/ 23 февраля 2009

Вот выстрел ..

Используйте инкрементную компиляцию, если ваша цепочка инструментов поддерживает это. (make, visual studio и т. д.).

Например, в GCC / make, если у вас есть много файлов для компиляции, но вы вносите изменения только в один файл, то компилируется только этот файл.

1 голос
/ 23 февраля 2009

В ответах на удивление отсутствует одна вещь: заставить вас делать грамматику без контекста и т. Д. Внимательно изучите языки, разработанные Виртом, такие как Паскаль и Модула-2. Вам не нужно переопределять Pascal, но дизайн грамматики сделан специально для быстрой компиляции. Тогда посмотрите, сможете ли вы найти какие-нибудь старые статьи о хитростях, которые вынудил Андерс реализовать Turbo Pascal. Подсказка: ведомый стол.

1 голос
/ 23 февраля 2009
  • Сделайте грамматику простой и недвусмысленной, а следовательно, быстрой и легкой для анализа.
  • Установить строгие ограничения на включение файла.
  • Разрешить компиляцию без полной информации, когда это возможно (например, предварительная декларация в C и C ++).
  • Компиляция за один проход, если возможно.
1 голос
/ 23 февраля 2009

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

Сколько вы можете разбить совместимые модули, и сколько вы хотите отслеживать их?

0 голосов
/ 23 февраля 2009

Создайте систему сборки, которая не сосет!

Существует огромное количество программ с, возможно, 3 исходными файлами, которые компилируются менее чем за секунду, но прежде чем вы доберетесь до этого, вам нужно будет пройти через скрипт automake, который занимает около 2 минут, проверяя такие вещи, как размер int. И если вы собираетесь скомпилировать что-то еще минуту спустя, это заставит вас пройти почти точно такой же набор тестов.

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

...