Чтобы дать глубокий ответ на вопрос " Почему виртуальная машина Java, а не интерпретатор Python? ", давайте попытаемся вернуться к области теории компиляции в качестве отправной точки обсуждения.
Типичный процесс компиляции программы включает следующие шаги:
- Лексический анализ . Разбивает текст программы на осмысленные «слова», называемые токенами (как часть процесса удаляются все комментарии, пробелы, новые строки и т. Д., Поскольку они не влияют на поведение программы). Результатом является упорядоченный поток токенов.
- Синтаксический анализ . Создает так называемое Абстрактное синтаксическое дерево (AST) из потока токенов. AST устанавливает отношения между токенами и, как следствие, определяет порядок оценки программы.
- Семантический анализ . Проверяет семантическую правильность AST, используя информацию о типах и набор семантических правил языка программирования. (Например,
a = b + c
является правильным утверждением с точки зрения синтаксиса, но совершенно неверным с семантической точки зрения, если a
было объявлено как постоянный объект)
- Промежуточная генерация кода . Сериализует AST в линейно упорядоченный поток машинно-независимых «примитивных» операций. Фактически, генератор кода обходит AST и регистрирует порядок шагов оценки. В результате из древовидного представления программы мы получаем гораздо более простое представление в виде списка, в котором сохраняется порядок оценки программы.
- Генерация машинного кода . Программа в виде машинно-независимого «примитивного» байт-кода преобразуется в машинный код конкретной архитектуры процессора.
Ok. Теперь давайте определимся с терминами.
Интерпретатор в классическом значении этого слова предполагает выполнение на основе оценки программы на основе AST, полученного непосредственно из программы text . В этом случае программа распространяется в виде исходного кода , а интерпретатор получает текст программы, часто динамическим образом (оператор за оператором или построчно). Для каждого оператора ввода интерпретатор создает свой AST и сразу же оценивает его, изменяя «состояние» программы. Это типичное поведение, демонстрируемое скриптовыми языками. Рассмотрим, например, Bash, Windows CMD и т. Д. Концептуально, Python тоже так поступает.
Если мы заменим шаг выполнения на основе AST на генерацию промежуточного машинно-независимого шага двоичного байт-кода в интерпретаторе, мы разделим весь процесс выполнения программы на две отдельные фазы: компиляция и выполнение. В этом случае то, что раньше было интерпретатором, станет компилятором байт-кода, который преобразует программу из формы text в некоторую двоичную форму. Затем программа распространяется в этой двоичной форме, но не в форме исходного кода. На пользовательской машине этот байт-код подается в новую сущность - виртуальную машину , которая фактически интерпретирует этот байт-код. В связи с этим виртуальные машины также называются интерпретатором байт-кода . Но обратите ваше внимание здесь! Классический интерпретатор - это текстовый интерпретатор , а виртуальная машина - это двоичный интерпретатор ! Это подход, принятый Java и C #.
Наконец, если мы добавим генерацию машинного кода в компилятор байт-кода, мы получим в результате то, что мы называем классическим компилятором . Классический компилятор преобразует исходный код программы в машинный код конкретного процессора. Затем этот машинный код может быть непосредственно выполнен на целевом процессоре без какого-либо дополнительного посредника (без какого-либо интерпретатора, ни текстового, ни двоичного интерпретатора).
Давайте вернемся к исходному вопросу и рассмотрим Java против Python.
Java изначально был спроектирован так, чтобы иметь как можно меньше зависимостей реализации. Его дизайн основан на принципе «пиши один раз, беги куда угодно» (WORA). Для его реализации Java изначально разрабатывался как язык программирования, который компилируется в машинно-независимый двоичный байт-код , который затем может выполняться на всех платформах, поддерживающих Java . без необходимости его перекомпиляции. Вы можете думать о Java , как о WORA C ++ . На самом деле Java ближе к C ++ , чем к языкам сценариев, таким как Python . Но в отличие от C ++ , Java был разработан для компиляции в двоичный байт-код , который затем выполняется в среде виртуальной машины в то время как C ++ был разработан для компиляции в машинном коде и последующего непосредственного выполнения целевым процессором.
Python изначально разрабатывался как разновидность языка программирования сценариев, который интерпретирует сценарии (программы в виде текста , написанного в соответствии с правилами языка программирования). Из-за этого Python изначально поддерживал динамическую интерпретацию однострочных команд или операторов, как это делают Bash или Windows CMD. По той же причине в начальных реализациях Python не было каких-либо компиляторов байт-кода и виртуальных машин для выполнения такого байт-кода внутри, но с самого начала Python требовал интерпретатор , который способен понять и оценить программу Python текст .
Из-за этого исторически Java разработчики имели тенденцию говорить о Виртуальной машине Java (потому что изначально Java представлял собой пакет Java компилятор байт-кода и интерпретатор байт-кода - JVM ) и Python разработчики, как правило, говорили о Python интерпретаторе (потому что изначально Python не имеет никакой виртуальной машины и был своего рода классическим текстовым интерпретатором , который выполняет программу text напрямую без какой-либо компиляции или преобразования в любой вид двоичного кода) .
В настоящее время Python также имеет виртуальную машину под капотом и может компилировать и интерпретировать байт-код Python. И этот факт делает дополнительные инвестиции в путаницу " Почему виртуальная машина Java, а интерпретатор Python? ", потому что кажется, что реализации обоих языков содержат виртуальные машины.
Но! Даже в настоящий момент интерпретация текста программы является основным способом выполнения программ на Python. Реализации Python используют виртуальные машины под капотом исключительно как метод оптимизации. Интерпретация двоичного байт-кода в виртуальной машине намного эффективнее, чем прямая интерпретация исходного текста программы. В то же время присутствие виртуальной машины в Python абсолютно прозрачно как для разработчиков языка Python, так и для разработчиков программ Python. Один и тот же язык может быть реализован в интерпретаторах с виртуальной машиной и без нее. Точно так же одни и те же программы могут выполняться в интерпретаторах как с виртуальной машиной, так и без нее, и эти программы будут демонстрировать абсолютно одинаковое поведение и одинаково выводить одинаковые выходные данные. Единственным заметным отличием будет скорость выполнения программы и объем памяти, потребляемый интерпретатором. Таким образом, виртуальная машина в Python не является неизбежной частью языкового дизайна, но является необязательным расширением основного интерпретатора Python.
Java можно рассматривать аналогичным образом. Java под капотом имеет JIT-компилятор и может выборочно компилировать методы класса Java в машинный код целевой платформы и затем непосредственно выполнять его. Но! Java по-прежнему использует интерпретацию байт-кода в качестве основного способа выполнения программы Java. Подобно реализациям Python, которые используют виртуальные машины под капотом исключительно как метод оптимизации, виртуальные машины Java используют компиляторы Just-In-Time исключительно для целей оптимизации. Точно так же, просто из-за того, что прямое выполнение машинного кода как минимум в десять раз быстрее, чем интерпретация Java байт-кода. Как и в случае с Python, наличие JIT-компилятора под капотом JVM абсолютно прозрачно как для разработчиков языка Java, так и для разработчиков программ на Java. Тот же язык программирования Java может быть реализован JVM с JIT-компилятором и без него. Точно так же одни и те же программы могут выполняться в JVM с JIT внутри и без него, и одни и те же программы будут демонстрировать одинаковое поведение и одинаково выводить одинаковые выходные данные на обоих входах JVM (с JIT и без него). И, как и в случае с Python, единственное заметное различие между ними заключается в скорости выполнения и объеме памяти, используемой JVM. И, наконец, как и в случае с Python, JIT в Java также не является неизбежной частью дизайна языка, но является необязательным расширением основных реализаций JVM.
С точки зрения проектирования и реализации виртуальных машин Java и Python они существенно различаются, хотя (внимание!) Обе остаются виртуальными машинами. JVM является примером низкоуровневой виртуальной машины с простыми базовыми операциями и высокой стоимостью отправки команд. Python, в свою очередь, является высокоуровневой виртуальной машиной, для которой инструкции демонстрируют сложное поведение, а стоимость отправки инструкций не столь значительна. Java работает с очень низким уровнем абстракции. JVM работает с небольшим четко определенным набором примитивных типов и имеет очень тесное соответствие (обычно один к одному) между инструкциями байт-кода и инструкциями собственного машинного кода. Напротив, виртуальная машина Python работает на высоком уровне абстракции, она работает со сложными типами данных (объектами) и поддерживает специальный полиморфизм, в то время как инструкции байт-кода демонстрируют сложное поведение, которое может быть представлено последовательностью нескольких инструкций машинного кода. Например, Python поддерживает математику неограниченного диапазона. Таким образом, Python VM вынуждена использовать длинную арифметику для потенциально больших целых чисел, для которых результат операции может переполнить машинное слово. Следовательно, одна инструкция байт-кода для арифметики в Python может быть представлена в вызове функции внутри Python VM, тогда как в JVM арифметическая операция будет представлена в виде простой операции, выраженной одной или несколькими собственными машинными инструкциями.
В результате мы можем сделать следующие выводы. Виртуальная машина Java, но интерпретатор Python, потому что:
- Термин «виртуальная машина» предполагает интерпретацию двоичного байт-кода, а термин «интерпретатор» - интерпретацию текста программы.
- Исторически Java была разработана и реализована для двоичной интерпретации байт-кода, а Python изначально был разработан и реализован для интерпретации текста программы. Таким образом, термин «виртуальная машина Java» является историческим и хорошо известен в сообществе Java. Точно так же термин «интерпретатор Python» является историческим и хорошо зарекомендовал себя в сообществе Python. Народы стремятся продлить традицию и использовать те же термины, которые использовались задолго до этого.
- Наконец, в настоящее время для Java интерпретация двоичного байт-кода является основным способом выполнения программ, в то время как JIT-компиляция является просто необязательной и прозрачной оптимизацией. А для Python в настоящее время интерпретация текста программы является основным способом выполнения программ Python, в то время как компиляция в байт-код Python VM является просто необязательной и прозрачной оптимизацией.
Следовательно, и Java, и Python имеют виртуальные машины, которые являются двоичными интерпретаторами байт-кода, что может привести к путанице, такой как « Почему Java Virtual Machine, но интерпретатор Python? ». Ключевым моментом здесь является то, что для Python виртуальная машина не является основным или необходимым средством выполнения программы; это просто необязательное расширение классического текстового интерпретатора. С другой стороны, виртуальная машина является ядром и неизбежной частью экосистемы выполнения Java-программ. Выбор статической или динамической типизации для дизайна языка программирования в основном влияет только на уровень абстракции виртуальной машины, но не определяет, нужна ли виртуальная машина. Языки, использующие обе системы ввода, могут быть скомпилированы, интерпретированы или выполнены в среде виртуальной машины в зависимости от желаемой модели выполнения.