Вы упростили утверждение Гвидо, сформулировав свой вопрос. Проблема не в написании компилятора для динамически типизированного языка. Проблема заключается в написании того, который (критерии 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). Вернуться к вышеприведенному абзацу.