В этом выступлении Гвидо ван Россум (27:30) рассказывает о попытках написать компилятор для кода Python, комментируя его следующим образом:
оказывается, что не так просто написать компилятор, который поддерживает все хорошие свойства динамической типизации, а также поддерживает семантическую корректность вашей программы, так что он фактически делает одно и то же, независимо от того, какую странность вы делаете где-то под прикрытием и фактически запускает немного быстрее
Каковы (возможные) проблемы, связанные с набором текста при написании компилятора для динамически типизированного языка, такого как Python?
exec
утверждении , которое вышло с версии 3.0 и, следовательно, выходит за рамки моего рассмотрения (и, возможно, высказывания Гвидо, поскольку речь идет о 2012 году). Не могли бы вы привести пример? И ваше определение «динамического определения объема», если оно [отличается от моего] (en.wikipedia.org/wiki/Dynamic_scoping).locals()
сохранения при вызовахlocals
. Что задокументировано и определенно не является деталью реализации, так это то, что даже нетlocals
или неglobals
может измениться область видения каждой переменной. Для каждого отдельного использования переменной область действия, к которой относится ссылка, определяется статически. Что делает его решительно лексически ограниченным. (И, между прочим,eval
иexec
определенно не детали реализации тоже - посмотрите на мой ответ!)Ответы:
Вы упростили утверждение Гвидо, сформулировав свой вопрос. Проблема не в написании компилятора для динамически типизированного языка. Проблема заключается в написании того, который (критерии 1) всегда корректен, (критерии 2) сохраняет динамическую типизацию и (критерии 3) заметно быстрее для значительного объема кода.
Легко внедрить 90% (не отвечая критериям 1) Python и быть последовательным в этом. Точно так же легко создать более быстрый вариант Python со статической типизацией (не соответствует критериям 2). Реализация 100% также проста (поскольку реализация сложного языка проста), но до сих пор каждый простой способ его реализации оказывается относительно медленным (не соответствует критериям 3).
Реализация интерпретатора плюс JIT - это правильно, реализует весь язык и быстрее для некоторого кода, оказывается выполнимым, хотя и значительно сложнее (см. PyPy), и только в том случае, если вы автоматизируете создание JIT-компилятора (Psyco обходился без него , но был очень ограничен в том, что код может ускорить). Но обратите внимание, что это явно выходит за рамки, так как мы говорим о статических(ака заранее) компиляторы. Я упоминаю об этом только для того, чтобы объяснить, почему его подход не работает для статических компиляторов (или, по крайней мере, не существует контрпримеров): сначала он должен интерпретировать и наблюдать программу, а затем генерировать код для конкретной итерации цикла (или другого линейного кода). путь), а затем оптимизировать его на основе допущений, истинных только для этой конкретной итерации (или, по крайней мере, не для всех возможных итераций). Ожидается, что многие последующие исполнения этого кода также будут соответствовать ожиданиям и, таким образом, выиграют от оптимизации. Некоторые (относительно дешевые) проверки добавляются для обеспечения правильности. Чтобы сделать все это, вам нужно понять, для чего нужно специализироваться, и медленную, но общую реализацию, к которой можно вернуться. Компиляторы AOT не имеют ни того, ни другого. Они не могут специализироваться вообщеоснованный на коде, который они не видят (например, динамически загружаемый код), и неосторожная специализация означает генерирование большего количества кода, который имеет ряд проблем (использование icache, размер двоичного файла, время компиляции, дополнительные ветви).
Реализация AOT-компилятора, который правильно реализует весь язык, также относительно проста: генерировать код, который обращается во время выполнения, чтобы делать то, что интерпретатор делал бы при подаче этого кода. Нуитка (в основном) делает это. Однако это не дает большого выигрыша в производительности (не соответствует критериям 3), поскольку вам все равно придется выполнять столько же ненужной работы, сколько и интерпретатору, за исключением отправки байт-кода в блок кода C, который выполняет то, что вы скомпилировали. Но это лишь сравнительно небольшая стоимость - достаточно значительная, чтобы ее можно было оптимизировать в существующем интерпретаторе, но недостаточно значительная, чтобы оправдать совершенно новую реализацию своими собственными проблемами.
Что потребуется для выполнения всех трех критериев? Мы понятия не имеем. Существуют некоторые схемы статического анализа, которые могут извлечь некоторую информацию о конкретных типах, потоке управления и т. Д. Из программ Python. Те, которые дают точные данные за пределами одного базового блока, являются чрезвычайно медленными и должны видеть всю программу или, по крайней мере, большую ее часть. Тем не менее, вы не можете ничего сделать с этой информацией, кроме, возможно, оптимизации нескольких операций над встроенными типами.
Почему это? Говоря прямо, компилятор либо лишает возможности выполнять код Python, загруженный во время выполнения (не соответствует критерию 1), либо не делает никаких предположений, которые вообще могут быть аннулированы любым кодом Python. К сожалению, это включает в себя почти все полезное для оптимизации программ: глобальные переменные, включая функции, могут быть восстановлены, классы могут быть видоизменены или полностью заменены, модули также могут быть изменены произвольно, импорт может быть захвачен несколькими способами и т. Д. Одна строка передается
eval
,exec
,__import__
или множество других функций, может сделать любое из этого. По сути, это означает, что практически невозможно применять большие оптимизации, что приводит к небольшому выигрышу в производительности (не соответствует критериям 3). Вернуться к вышеприведенному абзацу.источник
Самая сложная проблема состоит в том, чтобы выяснить, какой тип все имеет в любой момент времени.
В статическом языке, таком как C или Java, увидев объявление типа, вы знаете, что это за объект и что он может делать. Если переменная объявлена
int
, это целое число. Это, например, не ссылка на вызываемую функцию.В Python это может быть. Это ужасный Питон, но законный:
Теперь этот пример довольно глуп, но он иллюстрирует общую идею.
Более реалистично, вы могли бы заменить встроенную функцию пользовательской функцией, которая делает что-то немного другое (например, версия, которая записывает свои аргументы при вызове).
PyPy использует компиляцию Just-In-Time после просмотра того, что на самом деле делает код, и это позволяет PyPy значительно ускорить процесс. PyPy может наблюдать за циклом и проверять, что при каждом запуске цикла переменная
foo
всегда является целым числом; затем PyPy может оптимизировать код, который ищет тип приfoo
каждом проходе цикла, и часто может даже избавиться от объекта Python, представляющего целое число, иfoo
может просто стать числом, сидящим в регистре на ЦП. Вот как PyPy может быть быстрее, чем CPython; CPython делает поиск типов максимально быстро, но даже поиск не происходит даже быстрее.Я не знаю деталей, но я помню, что был проект под названием Unladen Swallow, который пытался применить технологию статического компилятора для ускорения Python (используя LLVM). Возможно, вы захотите поискать в Google Unladen Swallow и посмотреть, сможете ли вы найти обсуждение того, почему это не сработало, как они надеялись.
источник
Как говорит другой ответ, ключевой проблемой является выяснение информации о типе. Если вы можете делать это статически, вы можете напрямую генерировать хороший код.
Но даже если вы не можете сделать это статически, вы все равно можете генерировать разумный код, только во время выполнения, когда вы получаете фактическую информацию о типе. Эта информация часто оказывается стабильной или имеет не более нескольких различных значений для любого конкретного объекта в конкретной кодовой точке. Язык программирования SELF стал пионером многих идей агрессивного сбора типов во время выполнения и генерации кода во время выполнения. Его идеи широко используются в современных JIT-компиляторах, таких как Java и C #.
источник