Введение

Компиляторы и интерпретаторы — это компьютерные программы, которые переводят язык программирования высокого уровня в машинный язык.

Сегодня каждый программист работает либо с компилятором, либо с интерпретатором, а иногда и с тем и другим. Есть лишь некоторые крайние случаи, когда программистам приходится писать ассемблерный код самостоятельно. В 1950-е годы и раньше это был основной способ написания компьютерных программ. Таким образом, каждую программу приходилось переписывать на другом языке ассемблера, чтобы она могла работать на другой архитектуре. Первый коммерческий компилятор для Фортрана был написан в 1957 году компанией IBM, однако интерпретаторы использовались еще раньше. Эти интерпретаторы могли интерпретировать только языки ассемблера и использовались для перевода между наборами команд различных архитектур, что позволяло разрабатывать программное обеспечение для аппаратного обеспечения, которое все еще находится в разработке. Первым интерпретируемым языком высокого уровня стал Лисп, разработанный в 1958 году.

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

С точки зрения пользователя разница очевидна:

  • Компилятор считывает исходный код и создает исполняемую программу. Пользователь может запустить программу независимо от компилятора.
  • Интерпретатор читает исходный код и выполняет его построчно. Никакой исполняемый файл не создается, и для каждого выполнения всегда требуется интерпретатор. Кроме того, многие интерпретаторы имеют интерактивный режим, в котором вы можете вводить исходный код построчно, и каждая строка немедленно выполняется.

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

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

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

Компилятор

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

  1. Лексический анализ
  2. Разбор
  3. Семантический анализ
  4. Оптимизация
  5. Генерация кода

Процесс компиляции разделен на три этапа: внешний (шаги 1–3), средний (шаг 4) и внутренний (шаг 5).

Лексический анализ

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

Пример:

Токены: int, x, y, +, 5 и ;.

Разбор

Синтаксический анализ используется для идентификации грамматики путем построения абстрактного синтаксического дерева (AST). Давайте посмотрим на простой пример:

Как мы знаем, термин в круглых скобках должен быть обработан в первую очередь. Таким образом, парсер создаст следующее дерево:

На этом этапе синтаксический анализатор проверяет правильность синтаксиса и отображает возникающие ошибки.

Семантический анализ

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

Оптимизация

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

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

Генерация кода

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

Переводчик

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

Поскольку интерпретатор выполняет каждую инструкцию отдельно, его можно использовать в интерактивном режиме. Это упрощает разработку и отладку программ.

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

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

Интерпретаторы предметно-ориентированных языков (DSL) и интерпретаторы, используемые в образовательных целях, используют AST для выполнения инструкций. Этот процесс медленный, и интерпретаторы языков общего назначения ускоряют его выполнение, сначала компилируя исходный код в байт-код.

Байт/код — это виртуальный набор команд, который затем выполняется во время выполнения интерпретатора. Таким образом, более сложный интерпретатор состоит из компилятора для генерации байт-кода и среды выполнения для его выполнения. Примерами таких интерпретаторов являются Java, Python и JavaScript. Java даже немного более особенна, поскольку она также сохраняет свой байт-код (файлы .class и .jar), чтобы их можно было выполнить позже без необходимости предварительной перекомпиляции исходного кода. Тем не менее, для выполнения кода по-прежнему необходима виртуальная машина Java (JVM).

Сборка «точно в срок»

Компиляция «точно в срок» (JIT) представляет собой гибрид того и другого. Он используется внутри интерпретаторов для компиляции критически важного для производительности байт-кода во время выполнения в машинный код и его выполнения в исходном виде. Чтобы определить код, в котором выигрыш в производительности превышает затраты на компиляцию, интерпретатор анализирует код во время выполнения.

Несмотря на то, что уже были проведены исследования JIT-компиляторов для Lisp и Fortran, первый доступный JIT-компилятор был для Smalltalk в 1983 году. Сегодня этот метод используется виртуальной машиной Java, Microsoft .Net и PHP 8. Существует также PyPy, альтернативная реализация Python, которая предлагает JIT-компиляцию и другие языки сценариев, в настоящее время работающие над реализацией JIT-компилятора.

Сравнение компиляторов и интерпретаторов

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

Сравнение производительности

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

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

Разработка программного обеспечения

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

Но существуют также скомпилированные языки со средой выполнения, которые предоставляют такие функции, как сбор мусора, и предлагают обширную стандартную библиотеку для абстрагирования взаимодействия с оборудованием и ОС.

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

Это не то, что интерпретатор делает лучше, поскольку он не предлагает оптимизации кода, просто об этом следует помнить.

Гибкость и портативность

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

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

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

Безопасность

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

Заключение

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

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

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

Рекомендации

  1. CS143, Лекция Стэнфордского университета https://youtube.com/playlist?list=PLoCMsyE1cvdUZRe1udlyjpzTww1U5olL2
  2. Мастерские переводчики, Роберт Нистром, ISBN: 978-0990582939 https://craftinginterpreters.com/
  3. Интерпретатор (вычисления)) Википедия https://en.m.wikipedia.org/wiki/Interpreter_(computing)
  4. История создания компилятора в Википедии https://en.m.wikipedia.org/wiki/History_of_compiler_construction
  5. Сборник «точно в срок», Википедия https://en.m.wikipedia.org/wiki/Just-in-time_compilatio
  6. Джон Эйкок. 2003. Краткая история системы «точно в срок». АКМ Компьютер. Выж. 35, 2 (июнь 2003 г.), 97–113. https://doi.org/10.1145/857076.857077
  7. PyPy.org https://www.pypy.org/

Оригинально опубликовано на сайте https://mrs-th.hashnode.dev 23 августа 2023 г.