В чем разница между компиляцией и интерпретацией?
Я только что разговаривал с коллегой и где говорил о движке V8 JavaScript. Согласно Википедии,
V8 компилирует JavaScript в собственный машинный код [...] перед его выполнением, вместо более традиционных методов, таких как интерпретация байт-кода или компиляция всей программы в машинный код и выполнение его из файловой системы.
где (поправьте меня, если я ошибаюсь) " интерпретация байт-кода " - это способ работы Java, а " компиляция всей программы " будет применяться для таких языков, как C или C++. Теперь мы задавались вопросом, обсуждали и выдвигали ложные утверждения и предположения о различиях, сходствах. Чтобы покончить с этим, я рекомендовал обратиться к специалистам по SO.
Итак, кто может
- назвать, объяснить и / или ссылаться на все основные методы (например, прекомпиляция или интерпретация во время выполнения)
- визуализировать или представить схему об отношениях между источником, компиляцией и интерпретацией
- привести примеры (названия языков программирования) для основных методов #1.
Заметки:
- Я не ищу длинное прозаическое эссе о различных парадигмах, но визуально поддерживаемый, быстрый обзор.
- Я знаю, что Stackru не является энциклопедией для программистов (а скорее платформой для вопросов и ответов). Но так как я могу найти много популярных вопросов, которые дают энциклопедический взгляд на определенные темы (например, [1], [2], [3], [4], [5]), я начал этот вопрос.
- Если этот вопрос больше подходит для любого другого сайта StackExchange (например, cstheory), пожалуйста, дайте мне знать или пометить этот вопрос для модерации.
2 ответа
Почти невозможно ответить на ваш вопрос по одной простой причине: подходов немного, они скорее континуум. Фактический код, задействованный в этом континууме, также довольно идентичен, единственное отличие состоит в том, когда что-то происходит, и сохраняются ли промежуточные шаги каким-либо образом или нет. Различные точки на этом континууме (который не является одной линией, прогрессией, а скорее прямоугольником с разными углами, к которым вы можете быть близко):
- Чтение исходного кода
- Понимание кода
- Выполнение того, что вы поняли
- Кэширование различных промежуточных данных по дороге или даже постоянное сохранение их на диск.
Например, чисто интерпретируемый язык программирования в значительной степени не работает #4 и #2 вроде бы происходит неявно между 1 и 3, так что вы едва заметите это. Он просто читает разделы кода и немедленно реагирует на них. Это означает, что накладные расходы фактически начинаются, но, например, в цикле те же строки текста читаются и перечитываются снова.
В другом углу прямоугольника находятся традиционно скомпилированные языки, где обычно элемент № 4 состоит из постоянного сохранения фактического машинного кода в файл, который затем может быть запущен позднее. Это означает, что вы ждете сравнительно долго в начале, пока вся программа не будет переведена (даже если вы вызываете только одну функцию в ней), но циклы OTOH быстрее, потому что источник не нужно читать снова.
И затем между ними есть вещи, например, виртуальная машина: для переносимости многие языки программирования компилируются не с машинным кодом, а с байтовым кодом. Затем существует компилятор, который генерирует байт-код, и интерпретатор, который берет этот байт-код и фактически запускает его (фактически "превращая его в машинный код"). Хотя это обычно медленнее, чем компиляция и переход непосредственно к машинному коду, проще перенести такой язык на другую платформу, поскольку вам нужно только перенести интерпретатор байт-кода, который часто пишется на языке высокого уровня, то есть вы можете используйте существующий компилятор для "эффективного перевода в машинный код", и вам не нужно создавать и поддерживать бэкэнд для каждой платформы, на которой вы хотите работать. Кроме того, это может быть быстрее, если вы можете выполнить компиляцию для байт-кода один раз, а затем только распределить скомпилированный байт-код, чтобы другим людям не приходилось тратить циклы ЦП, например, на запуск оптимизатора над вашим кодом, и платить только за байт-код перевод на родной язык, который может быть незначительным в вашем случае использования. Кроме того, вы не раздаете свой исходный код.
Еще одна вещь между ними - это компилятор Just-in-Time (JIT), который фактически является интерпретатором, который хранит код, который он запускал один раз, в скомпилированной форме. Это "сохранение" делает его медленнее, чем чистый интерпретатор (например, добавляются служебные данные и использование ОЗУ, приводящее к обмену и доступу к диску), но делает это быстрее при многократном выполнении фрагмента кода. Он также может быть быстрее, чем чистый компилятор для кода, где, например, неоднократно вызывается только одна функция, потому что он не тратит время на компиляцию остальной части программы, если она не используется.
И, наконец, вы можете найти другие места в этом прямоугольнике, например, не сохраняя скомпилированный код постоянно, а снова удаляя скомпилированный код из кэша. Таким образом, вы можете, например, сэкономить место на диске или ОЗУ во встроенных системах, за счет того, что вам придется компилировать редко используемый фрагмент кода во второй раз. Многие JIT-компиляторы делают это.
Многие среды исполнения в настоящее время используют байт-код (или что-то подобное) в качестве промежуточного представления кода. Таким образом, исходный код сначала компилируется в промежуточный язык, который затем либо интерпретируется виртуальной машиной (которая декодирует набор инструкций байт-кода), либо компилируется далее в машинный код и выполняется аппаратным обеспечением.
Существует очень мало рабочих языков, которые интерпретируются без предварительной компиляции в какую-либо промежуточную форму. Однако такой интерпретатор легко осмыслить: просто подумайте об иерархии классов с подклассами для каждого типа элемента языка (if
заявление, for
и т. д.) и каждый класс, имеющий Evaluate
метод, который оценивает данный узел. Это также широко известно как шаблон проектирования интерпретатора.
В качестве примера рассмотрим следующий фрагмент кода, реализующий if
утверждение в гипотетическом интерпретаторе (реализовано в C#):
class IfStatement : AstNode {
private readonly AstNode condition, truePart, falsePart;
public IfStatement(AstNode condition, AstNode truePart, AstNode falsePart) {
this.condition = condition;
this.truePart = truePart;
this.falsePart = falsePart;
}
public override Value Evaluate(EvaluationContext context) {
bool yes = condition.Evaluate(context).IsTrue();
if (yes)
truePart.Evaluate(context);
else
falsePart.Evaluate(context);
return Value.None; // `if` statements have no value.
}
}
Это очень простой, но полностью функциональный переводчик.