Интерпретатор
Содержание:
- Содержание
- Компилируемые и интерпретируемые языки программирования
- Типы интерпретаторов
- Особенности Python
- Достоинства и недостатки интерпретаторов
- Типы языка
- Трансляторы
- Интерпретатор
- Достоинства и недостатки интерпретаторов
- Примечания
- Что такое компилятор?
- Основы
- Microsoft Visual Studio Community
- Достоинства и недостатки интерпретаторов
- Грамматика языка
- Токенизация
- Достоинства и недостатки интерпретаторов
- Эффективность
Содержание
Компилятор — это переводчик, который преобразует исходный язык (язык высокого уровня) в объектный язык (машинный язык). В отличие от компилятора, интерпретатор — это программа, имитирующая выполнение программ, написанных на исходном языке. Еще одно различие между компилятором и интерпретатором заключается в том, что компилятор преобразует всю программу за один раз, а интерпретатор преобразует программу, беря за раз одну строку.
Очевидно, что восприятие человека и электронного устройства, такого как компьютер, отличается. Люди могут понимать что угодно на естественных языках, но компьютер — нет. Компьютеру нужен переводчик для преобразования языков, написанных в удобочитаемой форме, в машиночитаемую форму.
Компилятор и интерпретатор — это типы языкового переводчика. Что такое языковой переводчик? Этот вопрос может возникнуть у вас в голове.
Языковой переводчик — это программное обеспечение, которое переводит программы с исходного языка в удобочитаемой форме в эквивалентную программу на объектном языке. Исходный язык обычно является языком программирования высокого уровня, а объектный язык — обычно машинным языком реального компьютера.
Компилируемые и интерпретируемые языки программирования
Теги:
- Языки программирования
- Технологии
Желающие освоить язык программирования сталкиваются с такими понятиями, как компилятор и интерпретатор. Компиляция и интерпретация — это основа работы языков программирования.
Языки программирования в общем подходе делятся на два класса — компилируемые и интерпретируемые. Стоит отметить, что эта классификация языков программирования на компилируемые и интерпретируемые, является весьма условной, поскольку для любого языка программирования может быть создан как компилятор, так и интерпретатор. Кроме того бывают языки программирования смешанного типа.
Мы полагаемся на такие инструменты, как компиляция и интерпретация, чтобы преобразовать наш код в форму, понятную компьютеру. Код может быть исполнен нативно, в операционной системе после конвертации в машинный (путём компиляции) или же исполняться построчно другой программой, которая делает это вместо ОС (интерпретатор).
Компилируемые языки
Программа на компилируемом языке при помощи специальной программы компилятора преобразуется (компилируется) в набор инструкций для данного типа процессора (машинный код) и далее записывается в исполняемый файл, который может быть запущен на выполнение как отдельная программа. Другими словами, компилятор переводит программу с языка высокого уровня на низкоуровневый язык, понятный процессору сразу и целиком, создавая при этом отдельную программу
Как правило, скомпилированные программы выполняются быстрее и не требуют для выполнения дополнительных программ, так как уже переведены на машинный язык. Вместе с тем при каждом изменении текста программы требуется ее перекомпиляция, что создает трудности при разработке. Кроме того, скомпилированная программа может выполняться только на том же типе компьютеров и, как правило, под той же операционной системой, на которую был рассчитан компилятор. Чтобы создать исполняемый файл для машины другого типа, требуется новая компиляция.
Компилируемые языки обычно позволяют получить более быструю и, возможно, более компактную программу, и поэтому применяются для создания часто используемых программ.
Примерами компилируемых языков являются Pascal, C, C++, Erlang, Haskell, Rust, Go, Ada.
Интерпретируемые языки
Если программа написана на интерпретируемом языке, то интерпретатор непосредственно выполняет (интерпретирует) ее текст без предварительного перевода. При этом программа остается на исходном языке и не может быть запущена без интерпретатора. Можно сказать, что процессор компьютера — это интерпретатор машинного кода. Кратко говоря, интерпретатор переводит на машинный язык прямо во время исполнения программы.
Программы на интерпретируемых языках можно запускать сразу же после изменения, что облегчает разработку. Программа на интерпретируемом языке может быть зачастую запущена на разных типах машин и операционных систем без дополнительных усилий. Однако интерпретируемые программы выполняются заметно медленнее, чем компилируемые, кроме того, они не могут выполняться без дополнительной программы-интерпретатора.
Примерами интерпретируемых языков являются PHP, Perl, Ruby, Python, JavaScript. К интерпретируемым языкам также можно отнести все скриптовые языки.
Многие языки в наши дни имеют как компилируемые, так и интерпретируемые реализации, сводя разницу между ними к минимуму. Некоторые языки, например, Java и C#, находятся между компилируемыми и интерпретируемыми. А именно, программа компилируется не в машинный язык, а в машинно-независимый код низкого уровня, байт-код. Далее байт-код выполняется виртуальной машиной. Для выполнения байт-кода обычно используется интерпретация, хотя отдельные его части для ускорения работы программы могут быть транслированы в машинный код непосредственно во время выполнения программы по технологии компиляции «на лету». Для Java байт-код исполняется виртуальной машиной Java (Java Virtual Machine, JVM), для C# — Common Language Runtime.
Перепечатка статьи допускается только при указании активной ссылки на сайт itmentor.by
Хочешь получать новые статьи первым? Вступай в сообщества ITmentor и
Типы интерпретаторов
Простой интерпретатор анализирует и тут же выполняет (собственно интерпретация) программу покомандно (или построчно), по мере поступления её исходного кода на вход интерпретатора. Достоинством такого подхода является мгновенная реакция. Недостаток — такой интерпретатор обнаруживает ошибки в тексте программы только при попытке выполнения команды (или строки) с ошибкой.
Интерпретатор компилирующего типа — это система из компилятора, переводящего исходный код программы в промежуточное представление, например, в байт-код или p-код, и собственно интерпретатора, который выполняет полученный промежуточный код (так называемая виртуальная машина). Достоинством таких систем является большее быстродействие выполнения программ (за счёт выноса анализа исходного кода в отдельный, разовый проход, и минимизации этого анализа в интерпретаторе). Недостатки — большее требование к ресурсам и требование на корректность исходного кода. Применяется в таких языках, как Java, PHP, Tcl, Perl, REXX (сохраняется результат парсинга исходного кода), а также в различных СУБД.
В случае разделения интерпретатора компилирующего типа на компоненты получаются компилятор языка и простой интерпретатор с минимизированным анализом исходного кода. Причём исходный код для такого интерпретатора не обязательно должен иметь текстовый формат или быть байт-кодом, который понимает только данный интерпретатор, это может быть машинный код какой-то существующей аппаратной платформы. К примеру, виртуальные машины вроде QEMU, Bochs, VMware включают в себя интерпретаторы машинного кода процессоров семейства x86.
Некоторые интерпретаторы (например, для языков Лисп, Scheme, Python, Бейсик и других) могут работать в режиме диалога или так называемого цикла чтения-вычисления-печати (англ. read-eval-print loop, REPL). В таком режиме интерпретатор считывает законченную конструкцию языка (например, s-expression в языке Лисп), выполняет её, печатает результаты, после чего переходит к ожиданию ввода пользователем следующей конструкции.
Уникальным является язык Forth, который способен работать как в режиме интерпретации, так и компиляции входных данных, позволяя переключаться между этими режимами в произвольный момент, как во время трансляции исходного кода, так и во время работы программ.
Следует также отметить, что режимы интерпретации можно найти не только в программном, но и аппаратном обеспечении. Так, многие микропроцессоры интерпретируют машинный код с помощью встроенных микропрограмм, а процессоры семейства x86, начиная с Pentium (например, на архитектуре Intel P6), во время исполнения машинного кода предварительно транслируют его во внутренний формат (в последовательность микроопераций).
Особенности Python
- Понятный синтаксис и простой в изучении
- Легко масштабируемый
- Бесплатный, с открытым исходным кодом и кроссплатформенный
- Объектно-ориентированный с высоким уровнем надежности и отличной читаемостью
- Может использоваться для прототипирования и тестирования, чтобы позже переходить к разработке на других высокоуровневых языках
- Предлагается с крупной библиотекой, включающей XML-парсеры и многое другое
Теперь посмотрим на основные отличия:
Параметр | C++ | Python |
---|---|---|
Компиляция | Компилируемый | Интерпретируемый |
Простота в использовании | Писать код непросто | Легко писать код |
Статическая/динамическая типизация | Статически типизируемый | Динамически типизируемый |
Портативность | Не портативный | Портативный |
Сборка мусора | Не поддерживает сборку мусора | Поддерживает сборку мусора |
Установка | Простая установка | Сложный в установке |
Типы | Типы данных проверяются при компиляции | Привязывается к значениям, проверяемым во время работы программы |
Область видимости переменных | Ограничены в пределах блоков и циклов | Доступны вне циклов или блоков |
Быстрое прототипирование | Невозможно | Возможно |
Функции | Ограничены по типу параметров и возвращаемому типу | Нет ограничений по типу параметров и возвращаемому типу |
Эффективность | Сложно поддерживать | Легко поддерживать |
Сложность синтаксиса | Использует блоки и точки с запятой | Нет блоков и точек с запятой (используются отступы) |
Скорость выполнения | Быстрый | Медленный |
Производительность | Высокая производительность | Низкая производительность |
Популярность | Более популярный во встроенных и энтерпрайз-системах | Наиболее популярен в машинном обучении |
Простота и удобство использования | Сложен в изучении и используется в низкоуровневых приложениях | Простой, используется в машинном обучении и веб-приложениях |
Достоинства и недостатки интерпретаторов
Достоинства
- Бо́льшая переносимость интерпретируемых программ — программа будет работать на любой платформе, на которой есть соответствующий интерпретатор.
- Как правило, более совершенные и наглядные средства диагностики ошибок в исходных кодах.
- Меньшие размеры кода по сравнению с машинным кодом, полученным после обычных компиляторов.
Недостатки
- Интерпретируемая программа не может выполняться отдельно без программы-интепретатора. Сам интерпретатор при этом может быть очень компактным.
- Интерпретируемая программа выполняется медленнее, поскольку промежуточный анализ исходного кода и планирование его выполнения требуют дополнительного времени в сравнении с непосредственным исполнением машинного кода, в который мог бы быть скомпилирован исходный код.
- Практически отсутствует оптимизация кода, что приводит к дополнительным потерям в скорости работы интерпретируемых программ.
Типы языка
Первый наш файл, yobaType.ml, который описывает все возможные виды инструкций, устроен максимально просто:
type action = DoNothing | AddFunction of string * action list | CallFunction of string | Stats | Create of string | Conditional of int * string * action * action | Decrement of int * string | Increment of int * string;;
Каждая конструкция языка будет приводиться к одному из этих типов. DoNothing — это просто оператор NOP, он не делает ровным счётом ничего. Create создаёт переменную (у нас они всегда целочисленны), Decrement и Increment соответственно уменьшают и увеличивают заданную переменную на какое-то число. Кроме этого есть Stats для вывода статистики по всем созданным переменным и Conditional — наша реализация if, которая умеет проверять, есть ли в заданной переменной требуемая величина (или большая). В самом конце я добавил AddFunction и CallFunction — возможность создавать и вызывать собственные функции, которые на самом деле очень даже процедуры.
Трансляторы
Так как текст записанной на Паскале программы не понятен компьютеру, то требуется перевести его на машинный язык. Такой перевод программы с языка программирования на язык машинных кодов называетсятрансляцией (translation — перевод), а выполняется он специальными программами —трансляторами.
Существует три вида трансляторов: интерпретаторы, компиляторы и ассемблеры.
Интерпретатором называется транслятор, производящий пооператорную (покомандную) обработку и выполнение исходной программы.
Компилятор преобразует (транслирует) всю программу в модуль на машинном языке, после этого программа записывается в память компьютера и лишь потом исполняется.
Ассемблеры переводят программу, записанную на языке ассемблера (автокода), в программу на машинном языке.
Любой транслятор решает следующие основные задачи:
• анализирует транслируемую программу, в частности определяет, содержит ли она синтаксические ошибки;
• генерирует выходную программу (ее часто называют объектной или рабочей) на языке команд ЭВМ (в некоторых случаях транслятор генерирует выходную программу на промежуточном языке, например, на языке ассемблера);
• распределяет память для выходной программы (в простейшем случае это заключается в назначении каждому фрагменту программы, переменным, константам, массивам и другим объектам своих адресов участков памяти).
Интерпретатор
Осталась последняя часть — сам интерпретатор, который обрабатывает наши конструкции языка.
open YobaType let identifiers = Hashtbl.create 10;; let funcs = Hashtbl.create 10;; let print_stats () = let print_item id amount = Printf.printf ">> Йо! У тебя есть %s: %d" id amount; print_newline (); flush stdout in Hashtbl.iter print_item identifiers;; let arithm id op value () = try Hashtbl.replace identifiers id (op (Hashtbl.find identifiers id) value); Printf.printf ">> Гавно вопрос\n"; flush stdout with Not_found -> Printf.printf ">> Х@#на, ты %s не любишь\n" id; flush stdout;; let rec cond amount id act1 act2 () = try if Hashtbl.find identifiers id >= amount then process_action act1 () else process_action act2 () with Not_found -> Printf.printf ">> Човаще?!\n"; flush stdout and process_action = function | Create(id) -> (function () -> Hashtbl.add identifiers id 0) | Decrement(amount, id) -> arithm id (-) amount | Increment(amount, id) -> arithm id (+) amount | Conditional(amount, id, act1, act2) -> cond amount id act1 act2 | DoNothing -> (function () -> ()) | Stats -> print_stats | AddFunction(id, funclist) -> (function () -> Hashtbl.add funcs id funclist) | CallFunction(id) -> callfun id and callfun id () = let f: YobaType.action list = Hashtbl.find funcs id in List.iter (function x -> process_action x ()) f ;; while true do try let lexbuf = Lexing.from_channel stdin in process_action (YobaParser.main YobaLexer.token lexbuf) () with YobaLexer.Eof -> print_stats (); exit 0 | Parsing.Parse_error -> Printf.printf ">> Ни@#я не понял б@#!\n"; flush stdout | Failure(_) -> Printf.printf ">> Ни@#я не понял б@#!\n"; flush stdout done
Первым делом мы создадим две хэштаблицы — для переменных и для функций. Начальный размер 10 взят от фонаря, у нас же тренировочный язык, зачем нам сразу много функций.
Затем объявим две небольших функции: одна — для вывода статистики, вторая — для инкремента/декремента переменных.
Дальше идёт группа из сразу трёх функций: cond обрабатывает условные конструкции (наш if), callfun отвечает за вызов функций, а process_action отвечает за обработку пришедшей на вход инструкции как таковой. Надеюсь, почему все три функции зависят друг от друга, объяснять не надо.
Обратите внимание, все варианты в process_action не выполняют действие, а всего лишь возвращают функцию, которая его выполнит. Изначально это было не так, но именно это маленькое изменение позволило мне легко и непринужденно добавить в язык поддержку пользовательских функций.. Наконец, последняяя часть кода до посинения в цикле читает и обрабатывает результат работы парсера.
Наконец, последняяя часть кода до посинения в цикле читает и обрабатывает результат работы парсера.
Добавим к этому Makefile:
all: ocamlc -c yobaType.ml ocamllex yobaLexer.mll ocamlyacc yobaParser.mly ocamlc -c yobaParser.mli ocamlc -c yobaLexer.ml ocamlc -c yobaParser.ml ocamlc -c yoba.ml ocamlc -o yoba yobaLexer.cmo yobaParser.cmo yoba.cmo clean: rm -f *.cmo *.cmi *.mli yoba yobaLexer.ml yobaParser.ml
Достоинства и недостатки интерпретаторов
Достоинства
- Бо́льшая переносимость интерпретируемых программ — программа будет работать на любой платформе, на которой есть соответствующий интерпретатор.
- Как правило, более совершенные и наглядные средства диагностики ошибок в исходных кодах.
- Меньшие размеры кода по сравнению с машинным кодом, полученным после обычных компиляторов.
Недостатки
- Интерпретируемая программа не может выполняться отдельно без программы-интерпретатора. Сам интерпретатор при этом может быть очень компактным.
- Интерпретируемая программа выполняется медленнее, поскольку промежуточный анализ исходного кода и планирование его выполнения требуют дополнительного времени в сравнении с непосредственным исполнением машинного кода, в который мог бы быть скомпилирован исходный код.
- Практически отсутствует оптимизация кода, что приводит к дополнительным потерям в скорости работы интерпретируемых программ.
Примечания
- Кочергин В. И. interpreter // Большой англо-русский толковый научно-технический словарь компьютерных информационных технологий и радиоэлектроники. — 2016. — ISBN 978-5-7511-2332-1.
- Интерпретатор // Математический энциклопедический словарь / Гл. ред. Прохоров Ю. В.. — М.: Советская энциклопедия, 1988. — С. 820. — 847 с.
- ГОСТ 19781-83; СТ ИСО 2382/7-77 // Вычислительная техника. Терминология: Справочное пособие. Выпуск 1 / Рецензент канд. техн. наук Ю. П. Селиванов. — М.: Издательство стандартов, 1989. — 168 с. — 55 000 экз. — ISBN 5-7050-0155-X.
- Першиков В. И., Савинков В. М. Толковый словарь по информатике / Рецензенты: канд. физ.-мат. наук А. С. Марков и д-р физ.-мат. наук И. В. Поттосин. — М.: Финансы и статистика, 1991. — 543 с. — 50 000 экз. — ISBN 5-279-00367-0.
- Борковский А. Б. Англо-русский словарь по программированию и информатике (с толкованиями). — М.: Русский язык, 1990. — 335 с. — 50 050 (доп,) экз. — ISBN 5-200-01169-3.
- Толковый словарь по вычислительным системам = Dictionary of Computing / Под ред. В. Иллингуорта и др.: Пер. с англ. А. К. Белоцкого и др.; Под ред. Е. К. Масловского. — М.: Машиностроение, 1990. — 560 с. — 70 000 (доп,) экз. — ISBN 5-217-00617-X (СССР), ISBN 0-19-853913-4 (Великобритания).
Что такое компилятор?
Компилятор — это компьютерная программа, которая преобразует код, написанный на языке программирования высокого уровня, в машинный код. Это программа, которая переводит читаемый человеком код на язык, понятный процессору компьютера (двоичные 1 и 0 бит). Компьютер обрабатывает машинный код для выполнения соответствующих задач.
Компилятор должен соответствовать правилу синтаксиса того языка программирования, на котором он написан. Однако компилятор — это всего лишь программа и не может исправить ошибки, обнаруженные в этой программе. Итак, если вы допустили ошибку, вам нужно внести изменения в синтаксис вашей программы. В противном случае он не будет компилироваться.
Основы
Начинающий программист представляет себе работу интерпретатора примерно так:
Программист пишет код, запускает его с помощью виртуальной машины, которая его исполняет. Для исполнения кода надо работать с железом — процессором, памятью, жестким диском, сетевой картой и так далее — для таких дел виртуальная машина обращается к операционной системе. Начинающий программист знает, что за счет того, что существуют интерпретаторы под различные операционные системы — код программы оказывается кроссплатформенным.
Чуть более опытный программист исследовал файлы, которые порождает JVM при исполнении программы и знает, что JVM исполняет не исходный код программы, а байт-код, представляющий собой аналог ассемблера для виртуального Java-процессора. Байт-код генерируется загрузчиком классов и помещается в файлы с расширением . Загрузка классов выполняется динамически, то есть когда виртуальная машина, сталкивается с неизвестным ей (не загруженным) классом — она обращается к загрузчику. Очень хорошо информация о байт-коде представлена в статье Java Bytecode Fundamentals .
Microsoft Visual Studio Community
Для индивидуальных или начинающих программистов Microsoft Visual Studio Community включает в себя много важных инструментов из коммерческих версий проекта. Вы получите в свое распоряжение IDE, отладчик, оптимизирующий компилятор, редактор, средства отладки и профилирования. С помощью этого пакета можно разрабатывать программы для настольных и мобильных версий Windows, а также Android. Компилятор C++ поддерживает большинство функций ISO C++ 11, некоторые из ISO C++ 14 и C++ 17. В то же время компилятор C уже безнадежно устарел и не имеет даже надлежащей поддержки C99.
Программное обеспечение также поставляется с поддержкой построения программ на C#, Visual Basic, F# и Python. В то время, когда я писал эту статью, на сайте проекта утверждалось, что Visual Studio Community 2015 «бесплатный инструмент для индивидуальных разработчиков, проектов с открытым исходным кодом, научных исследований, образовательных проектов и небольших профессиональных групп».
Достоинства и недостатки интерпретаторов
Достоинства
- Бо́льшая переносимость интерпретируемых программ — программа будет работать на любой платформе, на которой есть соответствующий интерпретатор.
- Как правило, более совершенные и наглядные средства диагностики ошибок в исходных кодах.
- Меньшие размеры кода по сравнению с машинным кодом, полученным после обычных компиляторов.
Недостатки
- Интерпретируемая программа не может выполняться отдельно без программы-интерпретатора. Сам интерпретатор при этом может быть очень компактным.
- Интерпретируемая программа выполняется медленнее, поскольку промежуточный анализ исходного кода и планирование его выполнения требуют дополнительного времени в сравнении с непосредственным исполнением машинного кода, в который мог бы быть скомпилирован исходный код.
- Практически отсутствует оптимизация кода, что приводит к дополнительным потерям в скорости работы интерпретируемых программ.
Грамматика языка
Любая конструкция, кроме запроса статистики (это у нас как бы служебная команда) и создания функции начинается и заканчивается ключевыми словами. Благодаря этому мы можем смело расставлять как угодно переносы строк и отступы. Кроме этого (мы же работаем с русским языком) я специально создал по паре инструкций для случаев, когда надо передавать и переменную, и значение. Позже увидите, зачем это было нужно. Итак, наш файл yobaParser.mly:
%{ open YobaType %} %token <string> ID %token <int> INT %token RULEZ %token GIVE TAKE %token WASSUP DAMN %token CONTAINS THEN ELSE %token FUCKOFF %token STATS %token MEMORIZE IS %token CALL %start main %type <YobaType.action> main %% main: expr { $1 } expr: fullcommand { $1 } | MEMORIZE ID IS fullcommandlist DAMN { AddFunction($2, $4) } fullcommandlist: fullcommand { $1 :: [] } | fullcommand fullcommandlist { $1 :: $2 } fullcommand: WASSUP command DAMN { $2 } | STATS { Stats } command: FUCKOFF { DoNothing } | GIVE ID INT { Increment($3, $2) } | GIVE INT ID { Increment($2, $3) } | TAKE ID INT { Decrement($3, $2) } | TAKE INT ID { Decrement($2, $3) } | RULEZ ID { Create($2) } | CALL ID { CallFunction($2) } | CONTAINS ID INT THEN command ELSE command { Conditional($3, $2, $5, $7) } | CONTAINS INT ID THEN command ELSE command { Conditional($2, $3, $5, $7) } %%
Первым делом мы вставляем заголовок — открытие модуля YobaType, который содержит наш тип action, описанный в самом начале. Для чисел и строк, не являющихся ключевыми словами языка (переменных) мы объявляем два специальных типа, которым указываем, что именно они в себе содержат. Для каждого из ключевых слов с помощью директивы %token мы создаём тоже свой тип, который будет идентифицировать это слово в грамматике. Можно было бы указать их все хоть в одну строчку, просто такая запись группирует всё по видам инструкций. Имейте в виду, что все созданные нами токены — это именно подстановочные типы, по которым парсер грамматики определяет, что ему делать. Обозвать их можно как угодно, то, как они будут выглядеть в самом языке, мы опишем позже. Указываем, что входной точкой для грамматики является main, и что возвращать он всегда должен объект типа action — инструкцию для интерпретатора. Наконец, после двух знаков %% мы описываем саму грамматику:
- Инструкция состоит либо из команды (fullcommand), либо из создания функции.
- Функция, в свою очередь, состоит из списка команд (fullcommandlist).
- Команда бывает либо служебной (STATS), либо обычной (command), в таком случае она должна быть обёрнута в ключевые слова.
- С обычной командой всё просто, даже расписывать не буду.
В фигурных скобках мы указываем, что делать при совпадении строки с данным вариантом, при этом $N обозначает N-ный член конструкции. Например, если мы встречаем «CALL ID» (ID — это строка, не забываем), то мы создаём инструкцию CallFunction, которой в качестве параметра передаём $2 (как раз ID) — имя вызываемой функции.
Токенизация
токенизируются
- Токенизация чисел
Числа преобразуются в двоичный вид, чтобы не преобразовывать их каждый раз, когда они встречаются в программе. Если числа встречаются только один раз, то рост производительности оказывается не таким большим, но в цикле с большим количеством вычислений это выгодно, потому что число уже представлено в виде, который может понять компьютер. - Пометка строк
Так как память ограничена, если в коде есть строка, которую можно использовать без изменений, то логично будет так и поступить. Например, может выводить «Hello, World» непосредственно из строки программы вместо выделения нового пространства, копирования строки и её вывода.
Чтобы упростить пропуск строк во время выполнения программы, мы также храним длину самой строки. - Поиск в таблице ключевых слов
Всё, что не является числом или строкой, может быть ключевым словом, поэтому нам нужно выполнять поиск по списку ключевых слов. Это тривиально на JavaScript, но совсем непросто на ассемблере!
После нахождения ключевого слова связанный с ним токен сохраняется в памяти программы (вместо всего ключевого слова целиком). В результате этого мы можем сэкономить много пространства, особенно когда команду типа можно сократить до одного байта! - Вычисление указателей на переменные
Имена переменных Retroputer BASIC значимы только до первых двух символов (на данный момент). Благодаря этому можно тривиальным образом искать переменную в массиве при помощи довольно простого математического выражения. Но даже в таком случае вычисления занимают время, поэтому было бы здорово, если бы нам не приходилось этого делать при встрече с переменной.
Retroputer BASIC будет вычислять индекс и хранить его вместе с именем переменной. Кроме имени переменной он также хранит длину переменной, чтобы ускорить выполнение программы. Это занимает большое количество пространства и не было бы хорошим решением для компьютеров с ограниченной памятью, но подходит для Retroputer BASIC.
Достоинства и недостатки интерпретаторов
Достоинства
- Большая переносимость интерпретируемых программ — программа будет работать на любой платформе, на которой есть соответствующий интерпретатор.
- Как правило, более совершенные и наглядные средства диагностики ошибок в исходных кодах.
- Меньшие размеры кода по сравнению с машинным кодом, полученным после обычных компиляторов.
Недостатки
- Интерпретируемая программа не может выполняться отдельно без программы-интерпретатора. Сам интерпретатор при этом может быть очень компактным.
- Интерпретируемая программа выполняется медленнее, поскольку промежуточный анализ исходного кода и планирование его выполнения требуют дополнительного времени в сравнении с непосредственным исполнением машинного кода, в который мог бы быть скомпилирован исходный код.
- Практически отсутствует оптимизация кода, что приводит к дополнительным потерям в скорости работы интерпретируемых программ.
Эффективность
Обычно программисту не нужно разбираться в байт-коде, да и с загрузчиком проблем особых не возникает. Однако надо знать, что интерпретатор Java очень медленный, имеет стековую архитектуру (сильно отличается от вашего центрального процессора) и это сильно ослабляет эффект от распараллеливания ваших программ. Почему при этом Java-программы работают достаточно быстро? — Эффективность обеспечивают компилятор времени исполнения (Just-In-Time, JIT) и динамический профилировщик.
JIT-компилятор принимает байт-код и генерирует машинный код, который исполняется не виртуальным процессором, а реальным. За счет JIT ваши программы на Java/Python могут (теоретически) выполняться также быстро, как написанные на С++. По сути, JIT выполняет ту же работу что и компилятор C++, но делает это прямо во время выполнения программы и не для всей программы, а для так называемого Common path (основного пути исполнения). Отсюда растут ноги многих статей, показывающих, что Java-программа работает также быстро как на С++.
Откуда JIT узнает что надо компилировать, а что не стоит? — Эти данные предоставляет динамический профайлер, который прямо во время выполнения собирает статистику вызова функций, использовании переменных и т.п. То есть в интерпретируемых языках помимо вашего кода выполняется еще что-то, при каждом вызове функции, инкрементирующее счетчики и не только.
В языках типа C++ профилирование кода тоже выполняется перед компиляцией, но делают это не все программисты. Для этого программа запускается на некоторых эталонных наборах данных и собирается статистика ее выполнения. Затем, эта статистика передается оптимизатору кода. Например, если небольшая функция вызывается внутри цикла — то ее есть смысл инлайнить (подставить тело функции вместо ее вызова), если же она почти никогда не вызывается — то смысла в этом нет.
В современных интерпретаторах также встроен оптимизатор кода. Оптимизация — очень трудоемкий процесс, который в Java выполняется прямо при выполнении программы — это тормозит работу. Однако, при оптимизации учитываются результаты профилирования — за счет этого, программа на Java может оказаться более эффективной по сравнению с С++ если:
- программа на С++ не оптимизировалась вообще или не выполнялось профилирование. Так, например, инлайнить обычно надо не более 5% функций, однако чтобы найти эти функции — нужно выполнить профилирование. Многие из читающих эту статью программистов выполняют профилирование своего кода? — Думаю не более 1%.
- программа на С++ профилировалась на одних данных, а работает — на совершенно других. В видео рассказывается как с этим столкнулась и боролась команда Яндекс.Браузер (оказалось, что пользователи работают с браузером не так как планировали разработчики);
- программа оптимизирована под одно железо, а запущена на совершенно другом.
По приведенным выше причинам, программы на интерпретируемых языках могут иметь сносную (на фоне компилируемых) производительность. Если на этом этапе вам уже кажется что ничего такого в вашем любимом JavaScript/Python нету — посмотрите вот эту статью , также можно найти научные изыскания вплоть до автоматического распараллеливания программ на JS .
Программист может указать какой JIT использовать, например JVM HotSpot поставляется в двух вариантах — клиент и сервер, основное их отличие типе JIT-компилятора и наборе оптимизаций. При запуске виртуальной машины вы можете передать опцию или .