Export to GitHub

2c-python - RussianComments.wiki


Introduction

(сочинение, объясняющее, почему автор писал транслятор так, а не иначе)

Details

Вообще, к компилятору интерпретируемого языка всегда есть два главных требования:

a) Увеличение скорости выполнения программ

b) 100% совместимость

Строго говоря, увеличить скорость - не проблема. Книжку Ахо и Ульмана в зубы - и вперед! В конце концов, мы (программисты) пишем трансляторы 50 лет, основные трюки оптимизирующей трансляции давно изучены.

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

1) Pyrex

2) Cython

3) Shed-skin

Из них самый быстрый код генерит Shed-skin, а более-менее используются другие два (Pyrex - для написания модулей расширения и склейки с С кодом, Cython (сам разработанный на базе Pyrex) - в какой-то большой программе символьной и не только математики).

Покажу, о какой несовместимости идет речь таким куском Питон кода, который выполняется интерпретатором, но не транслируется ни одним из трех трансляторов:

```

``` if system == 'Windows':

def is_alarm():

    return True

else:

def is_alarm():

    return False

Т.е функция is_alarm создается динамически с разным кодом. Или, в терминах интерпретатора Питона - переменной is_alarm присваиваются разные кодовые объекты.

Я не сомневаюсь, что данный конкретный код может быть оттранслирован любым из них после 'доработки напильником'. Но таких конструкций, использующих динамизм языка, очень много. Напильник сотрешь. И выходит, что

Странслированная в С Питон-программа должна полностью поддерживать среду Питон (в общем случае)

Мне лень самому выписывать внутренности Питона. Да и зачем ? Берем run-time интерпретатора, подкладываем его под сгенеренный С-код - и что ? Странслированная программа обращается к другому Питон модулю, который никто и не думает компилировать в С. Так что и интерпретатор целиком, то есть весь CPython считаю своим ран-таймом. После чего лечу манию величия, и понимаю, что я написал/пишу просто еще один модуль Питона, который реализует ф-ю трансляции в двоичный исполнимый код (через С).

Примечание первое. Почему транслирую в С.

Вообще, при прямых руках и ловких пальчиках программа, странслированная напрямую в двоичный код, будет несколько быстрее и гарантированно меньше, чем аналогичная программа, странслированная в двоичный код через промежуточное представление в виде С-кода. Это не обсуждается. Возникают только два вопроса. Первое. Компьютеров много, и не все из них PC. Второе. Если все подпрограммы написаны на C, стоит ли извращаться, вызывая их из маш. кода?

История Psyco - вам например. Отличное решение, близкая к совершенству работа. Вот там - да, там генерится двоичный код напрямую. Но. Проблему с кроссплатформенностью Psyco поимел. Вернее... (Гусары, молчать!). И двоичный код там естественен, так как в Psyco на ассемблере переписана часть библиотеки поддержки и интерпретатора. Но сейчас автор Psyco не поддерживает его для новых версий CPython, а занимается JT компилятором в PyPy. Уже несколько лет занимается...

То бишь о чем я... Да, о проблемах трансляции Питон-программ.

Другой момент, когда возникает несовместимость -- когда интерпретатор и компилятор используют разные синтаксические и/или семантические анализаторы. Кроме того, что это избыточно и коряво, это еще и гарантированный способ получить несовместимость. (И именно исходя из этого, на переходе с 2.6 на 3.0, команда CPython отцепила хвостовой локомотив от поезда - модуль compiler исключен из дистрибутива.)

А между тем тот же Shed-skin этот модуль использует.

Есть интерфейс на уровне AST-дерева. Можно было бы использовать его. Но я предпочел прийти на готовенькое и в качестве входных данных для трансляции использовать питоновский байт-код. Да, это "немножко" неудобно. Но по крайней мере всегда можно открыть соответствующую строчку в интерпретаторе и посмотреть точную семантику. Причем в терминах С.

Правда, прежде чем странслировать в С, байт-код пришлось немного рекомпилировать до уровня выражений и структурных операторов (результат рекомпиляции можно посмотреть в файлах с расширением .pycmd. Это своего рода промежуточный результат трансляции.) А полученный псевдокод уже транслируем в С. С СОХРАНЕНИЕМ ДИНАМИЧЕСКОЙ СРЕДЫ ПИТОНА !!!

А сохранение среды означает использование в качестве основной структуры данных PyFrameObject объекта Питона. Вообще в интерпретаторе ее получает в качестве единственного параметра ф-я PyEval_EvalFrame. Она содержит в себе ссылки на локальные и глобальные переменные, на выполняемый байт-код, на константы. Короче говоря, она полностью определяет контекст вычислений. И интерпретатор берет ее, раскрывает ее в локальные С-переменные, и обрабатывает код в ней.

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