Как работают интерпретируемые языки (например, Ruby)? - PullRequest
6 голосов
/ 05 июля 2011

Я собираюсь выучить Ruby.Я знаю, что это интерпретированный язык.Я знаю, что скомпилированные языки в конечном итоге переводятся в машинный код, но что делает интерпретатор ruby?Я прочитал, что интерпретатор был написан на C, но конвертирует ли каждая строка ruby ​​в c, что снова компилируется в машинный код?Я также слышал о JIT, но если это добавляет сложности к ответу, вам не нужно отвечать на него.Что я ищу, так это то, что происходит с моим кодом Ruby.

1 Ответ

8 голосов
/ 05 июля 2011

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

Эта машина отражает физическую, по крайней мере, насколько это разумно и полезно. Он часто имеет память для инструкций, счетчик программ, стек для хранения промежуточных значений и адресов возврата и т. Д. Некоторые более сложные машины также имеют регистры. Существует фиксированный и относительно примитивный (по сравнению с языками вроде Ruby, а не по сравнению с реальными наборами команд ЦП) набор команд. Как и процессор, виртуальная машина бесконечно зацикливается:

  • Считать текущую инструкцию (определяется счетчиком программы).
  • (Расшифровывает, хотя обычно это намного проще, чем в реальных процессорах, по крайней мере, чем CISC.)
  • Выполняет его (вероятно, управляя стеком и / или регистрами в процессе).
  • Обновляет счетчик программы.

С переводчиком все это происходит через слой косвенности. Ваш реальный физический процессор понятия не имеет, что он делает. Виртуальная машина является программным обеспечением, каждый из вышеперечисленных шагов делегируется ЦПУ в несколько (в случаях с довольно высокоуровневыми инструкциями байт-кода, возможно, в десятки или сотни) физических циклов ЦП. И это происходит каждый раз, когда читается инструкция.

Войти в JIT-компиляцию. Самая простая форма просто заменяет каждую инструкцию байт-кода на (несколько оптимизированную) копию кода, которая будет выполняться, когда интерпретатор встретит ее. Это уже дает выигрыш в скорости, например манипуляции со счетчиком программ могут быть опущены. Но есть и более разумные варианты.

Отслеживание JIT, например, начинается как обычный переводчик и дополнительно наблюдает за программой, которую они выполняют. Если они заметят, что программа проводит много времени в определенном разделе кода (почти всегда, в цикле или функции, вызываемой из циклов), она начинает записывать, что она делает во время этого - она ​​генерирует trace, Когда он достигает точки, где он начал запись (после одной итерации цикла), он называет это днем ​​и компилирует трассировку в машинный код. Но так как он видел, как программа на самом деле ведет себя во время выполнения, он может генерировать код, который точно соответствует этому поведению. Возьмем, к примеру, цикл добавления целых чисел. Машинный код не будет содержать никаких проверок типов и вызовов функций, которые фактически выполняет интерпретатор. По крайней мере, он не будет содержать большинство из них. Это будет , чтобы гарантировать правильность, добавить проверки, что условия, при которых трасса была записана (например, переменные являются целыми числами), все еще сохраняются. Когда такая проверка не проходит, она спасается и возобновляет интерпретацию, пока не будет записана другая трасса. Но пока это не произойдет, он мог бы выполнить сотню итераций на скорости, равной рукописному коду С.

...