Компилируемость vs Интерпретируемость#

Всем известно, что python является интерпретируемым языком программирования, а C/C++компилируемым. Снизу приводятся определения из Wikipedia (1 и 2):

Компилятор — программа, переводящая текст, написанный на языке программирования, в набор машинных кодов.

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

Однако, если пытаться углубиться и провести черту между интерпретируемыми и компилируемыми языками программирования, то оказывается, что не всё так просто. Например, существует интерпретатор для C++ cling, а некоторые интерпретаторы python (например, PyPy) и библиотеки для python (например, numba) осуществляют JIT-компиляцию исходного кода python в машинный код.

Тем не менее стандартный режим работы с C/C++ включает в себя этап компиляции, а наиболее распространенный стандартный интерпретатор CPython для python не поддерживает JIT-компиляцию. Поэтому, для того чтобы обрисовать разницу между компилируемостью и интерпретируемостью, воспользуемся этими языками в качестве примеров.

Компилятор C/С++#

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

../_images/Compiler_design.png
  • Первая часть (front end) просматривает файлы исходного кода на высокоуровневом языке программирования (например, файлы .cpp), и в первую очередь осуществляет проверки: корректность синтаксиса, согласованность типов (C/C++ статически типизированный язык) и т.п. Если обнаруживается ошибка, то генерируется ошибка компиляции. Если ошибок нет, то результатом работы первого блока является внутреннее (промежуточное) представление программы, которые абстрагировано от исходного высокоуровневого языка.

  • Вторая часть (middle end) принимает на вход это промежуточное представление, анализирует его и оптимизирует из соображений логики программы. Например, если какая-то ветвь программы недостижима, то она может быть выкинута, или, например, если компилятор обнаруживает способ переорганизовать вычисления, гарантирующий одновременно неизменный результат и меньшее время исполнения, то он может его применить в зависимости от настроек компиллятора. Однако, важно понимать, что на этом этапе ещё не учитывается информация об архитектуре целевой машины, и уже не учитывается информация об особенностях исходного языка программирования.

  • Третья часть (back end) принимает на вход оптимизированное промежуточное представление и транслирует его в машинный код для конкретной машины, попутно производя оптимизации, специфичные для архитектуры этой машины.

Такой трёх этапный процесс компиляции позволяет использовать один и тот же middle end для разных языков программирования и архитектур процессоров, меняя front end и back end.

Интерпретатор python#

Уже упоминалось, что существует несколько разных интерпретаторов для python, однако CPython является самым популярным их них с большим заделом. CPython — интерпретатор для python, написанный на языке C. Под установкой python чаще всего имеют в виду установку интерпретатора CPython.

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

Интерпретатор vs. компилятор#

  1. Требования к наличию транслятора:

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

    • интерпретатор требуется при каждом запуске программы. Без интерпретатора запустить программу не удастся.

  2. Возможность оптимизации:

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

    • Так как интерпретатор видит код программы кусками, то он не может оценить возможные сценарии её работы и возможности оптимизации сводятся к минимуму.

  3. Интерактивность:

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

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

  4. Кроссплатформенность:

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

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