Які проблеми пов’язані з набором тексту під час написання компілятора для динамічно набраної мови?


9

У цій розмові Гідо ван Россум розповідає (27:30) про спроби написати компілятор для коду Python, коментуючи це, кажучи:

виявляється, не так просто написати компілятор, який підтримує всі приємні властивості динамічного набору тексту, а також підтримує семантичну коректність вашої програми, щоб він насправді робив те саме, незалежно від того, яку дивність ви робите десь під обкладинками і насправді працює швидше

Які (можливі) проблеми пов'язані з набором тексту під час написання компілятора для динамічно набраної мови, як-от Python?


У цьому випадку динамічне введення тексту не є чи не найбільшою проблемою. Для python - це динамічне оцінювання.
SK-логіка

Варто зазначити, що інші люди стверджували, що побудова динамічного введення в платформу - це правильна відповідь. Майкрософт вклав багато грошей у DLR саме з цієї причини - і NeXT / Apple вже десятиліттями проходить на цьому шляху. Це не допомагає CPython, але IronPython доводить, що ви можете ефективно статично компілювати Python, а PyPy доводить, що цього не потрібно.
abarnert

2
@ SK-логіка Динамічне оцінювання в Python? Востаннє я перевірив, чи всі конструкції в мові використовують лексичне визначення.

1
@ SK-логіка Ви можете динамічно створювати код і виконувати його, але цей код також працює в лексичному масштабі. Для кожної окремої змінної в програмі Python ви можете легко визначити, якій області змінної належить лише оглянувши AST. Ви можете думати про execтвердження , яке минуло з 3.0 і, отже, поза моїм розглядом (і, мабуть, у Гуйдо, як це розмова з 2012 року). Чи можете ви навести приклад? І ваше визначення "динамічного масштабування", якщо воно [відмінне від мого] (en.wikipedia.org/wiki/Dynamic_scoping).

1
@ SK-логіка Єдине, що для мене детально реалізує, - це зміни, щоб повернути значення, що locals()зберігаються в усіх дзвінках locals. Що документовані і , безумовно , не є деталлю реалізації є те , що навіть не localsчи globalsможе змінитися в якому обсязі кожна змінна шукається. Для кожного використання змінної, область , до якої відноситься це статично визначається. Що робить його, безумовно, лексично обстеженим. (І btw, evalі execточно не є деталями впровадження - подивіться на мою відповідь!)

Відповіді:


16

Ви надто спростили твердження Гідо, формулюючи своє запитання. Проблема полягає не в написанні компілятора для динамічно набраної мови. Проблема полягає в тому, щоб записати той, який (критерії 1) завжди правильний, (критерії 2) зберігає динамічне введення тексту, а (критерії 3) помітно швидше для значної кількості коду.

Реалізувати 90% (невдалі критерії 1) Python легко і бути послідовно швидким. Так само легко створити більш швидкий варіант Python зі статичним набором тексту (невдалі критерії 2). 100% реалізація також проста (якщо реалізувати складну мову легко), але поки що кожен простий спосіб її здійснення виявляється відносно повільним (невдалі критерії 3).

Реалізація інтерпретатора плюс правильний JIT , реалізація всієї мови, і швидше для деякого коду виявляється здійсненною, хоча значно складніше (пор. PyPy), і тільки так, якщо ви автоматизуєте створення компілятора JIT (Psyco не обійшлося без цього , але був дуже обмежений у тому, який код може пришвидшити). Але зауважте, що це явно не виходить за межі, оскільки ми говоримо про статичне(він же заздалегідь) компіляторів. Я згадую це лише для пояснення, чому його підхід не працює для статичних компіляторів (або, принаймні, немає існуючого контрприкладу): він спочатку повинен інтерпретувати та спостерігати за програмою, а потім генерувати код для конкретної ітерації циклу (або іншого лінійного коду path), а потім оптимізуйте пекло, виходячи з припущень, що стосуються лише конкретної ітерації (або, принаймні, не для всіх можливих ітерацій). Очікується, що багато пізніших виконання цього коду також відповідатимуть очікуванням і, таким чином, виграють від оптимізацій. Для забезпечення коректності додаються деякі (відносно дешеві) чеки. Щоб зробити все це, вам потрібно уявити, для чого слід спеціалізуватися, і повільну, але загальну реалізацію, до якої слід повернутися. У компіляторів AOT немає жодного. Вони не можуть спеціалізуватися на всіхна основі коду, який вони не можуть бачити (наприклад, динамічно завантажений код), а спеціалізація недбало означає генерувати більше коду, що має ряд проблем (використання icache, використання двійкового розміру, час компіляції, додаткові гілки).

Реалізація компілятора AOT, який правильно реалізує всю мову, також відносно проста: Створіть код, який запускає час виконання, щоб робити те, що зробить інтерпретатор, коли він подається з цим кодом. Нуїтка (в основному) робить це. Однак це не приносить великої користі для продуктивності (невдалі критерії 3), оскільки вам все одно доведеться виконувати так само непотрібну роботу, як інтерпретатор, за винятком того, щоб відправити байт-код до блоку коду С, який виконує те, що ви склали. Але це лише досить невелика вартість - достатньо значна, щоб її варто було оптимізувати в існуючому перекладачі, але недостатньо істотна, щоб виправдати цілком нову реалізацію власними проблемами.

Що потрібно для виконання всіх трьох критеріїв? Ми поняття не маємо. Існують деякі схеми статичного аналізу, які можуть отримати певну інформацію про конкретні типи, контрольний потік тощо з програм Python. Ті, які видають точні дані за межі одного базового блоку, надзвичайно повільні і потребують перегляду всієї програми або принаймні більшості її. Тим не менш, ви не можете багато зробити з цією інформацією, крім можливо оптимізувати кілька операцій над вбудованими типами.

Чому це? Якщо сказати прямо, компілятор або видаляє можливість виконання коду Python, завантаженого під час виконання (невдалі критерії 1), або не робить жодних припущень, які взагалі можуть бути визнані недійсними будь-яким кодом Python. На жаль, це включає в себе майже всі , що корисно для оптимізації програм: Globals включаючи функцію може бути перевизначені, класи можуть бути мутують або повністю замінено, модулі можуть бути змінені довільно занадто, імпорт можуть захопити кілька способів, і т.д. Одна рядки передається eval, exec, __import__або численні інші функції, можуть виконувати будь-що з цього. Насправді, це означає, що майже не можна застосовувати великих оптимізацій, що дасть невелику користь від продуктивності (невдалі критерії 3). Повернення до вищевказаного абзацу.


4

Найскладніша проблема - з’ясувати, який тип має все в даний момент часу.

Статичною мовою, такою як C або Java, щойно ви побачили декларацію типу, ви знаєте, що таке об'єкт і що він може робити. Якщо оголошена змінна int, це ціле число. Наприклад, це не посилання на функцію дзвінка.

У Python це може бути. Це жахливий Python, але законний:

i = 2
x = 3 + i

def prn(s):
    print(s)

i = prn
i(x)

Зараз цей приклад досить дурний, але він ілюструє загальну ідею.

Більш реально, ви можете замінити вбудовану функцію на визначену користувачем функцію, яка робить щось дещо інше (наприклад, версія, яка записує свої аргументи, коли ви викликаєте її).

PyPy використовує компіляцію Just-In-Time після перегляду того, що насправді робить код, і це дозволяє PyPy значно прискорити роботу. PyPy може дивитися цикл і перевіряти, що кожного разу, коли цикл працює, змінна fooзавжди є цілим числом; тоді PyPy може оптимізувати код, який шукає тип fooкожного проходу через цикл, і часто навіть може позбутися об'єкта Python, який представляє ціле число, і fooможе просто стати числом, що знаходиться в реєстрі на процесорі. Ось як PyPy може бути швидшим, ніж CPython; CPython робить пошук типу максимально швидким, але навіть пошук не стає ще швидшим.

Я не знаю деталей, але я пам’ятаю, що був проект під назвою «Безладна ластівка», який намагався застосувати статичну технологію компілятора, щоб пришвидшити Python (використовуючи LLVM). Можливо, ви захочете пошукати Ластівку без навантаження в Google і дізнатись, чи зможете ви знайти дискусію, чому це не вийшло, як вони сподівалися.


Ластівка без навантаження не стосувалася статичної компіляції чи статичних типів; остаточне перетворення було ефективно перенести інтерпретатора CPython, з усією його динамічністю, на LLVM, з фантазійним новим JIT (на кшталт Parrot, або DLR для .NET… або PyPy, справді), хоча чим вони насправді і закінчилися це було знайти багато локальних оптимізацій в CPython (деякі з яких перетворили його на mainline 3.x). Shedskin - це, мабуть, проект, про який ви думаєте, що використовував статичний висновок про статичну компіляцію Python (хоча до C ++, а не безпосередньо до рідного коду).
abarnert

Один з авторів "Ненавантаженої ластівки", Рейд Клекнер, опублікував ретроспективу " Ластовиця без навантаження", яку, можливо, варто прочитати в цьому контексті, хоча насправді йдеться більше про проблеми управління та спонсорства, ніж про технічні.
abarnert

0

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

Але навіть якщо ви не можете зробити це статично, ви все одно можете генерувати розумний код, просто під час виконання, коли ви отримуєте фактичну інформацію про тип. Ця інформація часто виявляється стабільною або має максимум кілька різних значень для будь-якої конкретної сутності в конкретній точці коду. Мова програмування SELF започаткувала багато ідей агресивного збору типів виконання та створення коду виконання. Її ідеї широко використовуються в сучасних компіляторах на базі JIT, таких як Java та C #.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.