Грааль і Трюфель (Graal & Truffle)

Маловідомий дослідний проект, який може значно прискорити інновації в проектуванні мов програмування

Від перекладача
Хочу відразу попередити, що стаття місцями нагадує презентацію великої компанії з-за епітетів у дусі «змінить індустрію», «кращий на ринку», «проривні технології» та ін Якщо закрити очі на такий емоційний стиль оповіді, то вийде цікава вступна стаття про новинки технологій компіляторів і віртуальних машин.
Введення
З часів розквіту комп'ютерної індустрії багато були захоплені квестом в пошуках ідеального мови програмування. Квест дуже складний: створення нової мови — завдання не з легких. І дуже часто в процесі відбувається дроблення ситуації екосистеми програмування і виникає необхідність заново будувати базові інструменти для нової мови: компілятор, відладчик, HTTP стек, IDE, бібліотеки і нескінченне число базових блоків пишуться з нуля для кожної нової мови. Досконалість в дизайні мов програмування недосяжно, і нові ідеї виникають постійно. Ми схожі на Сізіфа: засудженого богами на вічне штовхання каменю в гору, щоб у підсумку побачити, як той скочується вниз знову і знову… цілу вічність.
Як можна розірвати цей порочний цикл? Давайте помріємо, чого б нам хотілося.
Нам потрібно щось, спеціальний інструмент, який дозволить зробити наступне:
  1. Спосіб створити новий мову всього за тиждень
  2. І щоб він автоматично працював так само швидко, як інші мови
  3. Щоб у нього була підтримка якісного відладчика, автоматично (в ідеалі без уповільнення роботи програми)
  4. Підтримка профілювання, автоматично
  5. Наявність якісного збирача сміття, автоматично… але тільки, якщо він нам знадобиться
  6. Щоб мова могла використовувати весь існуючий код, незалежно від того, на чому він був написаний
  7. Щоб мова підтримував будь-який стиль програмування від низькорівневого C або FORTRAN до Java, Haskell і повністю динамічних скриптових мов, таких як Python і Ruby.
    Щоб підтримував just-in-time і ahead-of-time компіляцію
  8. І нарешті, щоб підтримувалася гаряча заміна коду (hotswap) вже працюючої програми.
А ще, ми, звичайно, хочемо мати відкритий вихідний код цього чарівного інструмента. І нехай там будуть поні. Начебто все.
Розумний читач звичайно ж здогадався, що я не став би починати цю статтю, якщо б у мене не було такого інструменту. Звуть його трохи дивно — Graal & Truffle. І нехай назва скоріше підходить до претензійному хипстерскому ресторану, по суті це значний дослідницький проект, в якому беруть участь понад 40 науковців з IT індустрії і університетів. Разом вони будують нові технології компіляторів і віртуальних машин, які реалізують всі пункти з нашого списку.
Вони винайшли новий спосіб швидкого створення мов програмування без проблем з бібліотеками, що оптимізують компіляторами, отладчиками, профилировщиками, прив'язки до C бібліотек та іншими атрибутами, які необхідні сучасній мові програмування. Цей інструмент обіцяє викликати хвилю нових інновацій в мовах програмування і — я сподіваюся — переформатує всю IT індустрію.
Ось про це і поговоримо.
Що таке Graal і Truffle?
Graal — дослідний компілятор. А Truffle — це… це така штука, яку важко з чимось порівняти. Якщо коротко, то на думку спадає таке: Truffle — це фреймворк для створення мов програмування через написання інтерпретатора для абстрактного синтаксичного дерева.
При створенні нової мови програмування перший справою визначається граматика. Граматика — це опис синтаксичних правил вашої мови. Використовуючи граматику і інструмент на зразок ANTLR ви отримаєте парсер. На виході програми ви будете мати дерево парсинга

На картинці зображено дерево, яке отримано після роботи ANTLR для наступного рядка коду:
Abishek AND (country = India OR City = BLR) LOGIN 404 | show name


Дерево парсинга і похідна з неї конструкція під назвою абстрактне синтаксичне дерево (AST) — це природний спосіб вираження програм. Після побудови такого дерева залишиться один простий крок до працюючої програми. Вам потрібно буде додати метод «execute» в клас вузла дерева. Кожен вузол при виконанні «execute» може викликати дочірні вузли і комбінувати отримані результати для отримання значення виразу або виконання інструкції. І, в принципі, це все!
Візьмемо для прикладу динамічні мови програмування, такі як Python, JavaScript, PHP і Ruby. Для цих мов їх динамізм став результатом руху по шляху найменшого опору. Якщо ви створюєте мову з нуля, то ускладнення мови статичною системою типів або оптимізуючих компілятором може сильно уповільнити розробку мови. Наслідки такого вибору виливаються в низькій продуктивності. Але що ще гірше, виникає спокуса швидко додавати фічі в простій/повільний інтерпретатор AST. Адже оптимізувати продуктивність цих фіч згодом буде дуже складно.
Truffle — це фремворк для написання інтерпретаторів з використанням анотацій і невеликого кількість додаткового коду. Truffle в парі з Graal дозволить конвертувати такі інтерпретатори в JIT компилирующие віртуальних машин (VM)… автоматично. Отримана середовище виконання в моменти пікового продуктивності може змагатися з кращими існуючими компіляторами (налаштованими вручну і заточеними під конкретний мову). Наприклад отримана таким чином реалізація мови JavaScript під назвою TruffleJS може змагатися з V8 в тестах продуктивності.
Движок RubyTruffle швидше ніж всі інші реалізації Ruby. Движок TruffleC приблизно змагається з GCC. Вже існують реалізації деяких мов (різного ступеня готовності) з використанням Truffle:
  • JavaScript
  • Python 3
  • Ruby
  • LLVM bitcode — дозволяє запускати програми на C/C + + /Objective-C/Swift
  • Ще движок для інтерпретування вихідного коду на C без компіляції його в LLVM (про переваги такого підходу читайте нижче)
  • R
  • Smalltalk
  • Lua
  • Безліч маленьких експериментальних мов
Щоб вам стало зрозуміло, наскільки просто створювати ці движки, вихідні код TruffleJS займає всього близько 80 000 рядків, порівняно з 1.7 мільйонами рядків коду V8.
До біса подробиці, як мені погратися з усім цим?
Graal і Truffle — результат роботи Oracle Labs, частини Java команди, яка працює над дослідженнями в області віртуальних машин. GraalVM лежить тут. Це розширений Java Development Kit, який містить в собі деякі мови із зазначеного вище списку, а також замінники консольних утиліт NodeJS, Ruby і R. постачання також входить навчальний мову програмування «SimpleLanguage», з якого можна почати знайомство з Graal і Truffle.
Для чого потрібен Graal?
Якщо Truffle — це фреймворк для створення AST інтерпретаторів, то Graal — це штука для прискорення таких інтерпретаторів. Graal — це твір мистецтва в області оптимізуючих компіляторів. Він підтримує наступні системи:
  • може працювати в режимах just-in-time і ahead-of-time
  • дуже просунуті оптимізації, що включають partial escape analysis. Escape analysis дозволяє уникнути алокації об'єктів в купі, якщо це по суті не потрібно. Популярність EA зобов'язана розвитку JVM, але ця оптимізація дуже складна, і підтримується малим кількість віртуальних машин. JavaScript компілятор «Turbofan», що використовується в браузері Chrome, почав отримувати escape analysis оптимізації тільки в кінці 2015 року. В той час, як у Graal є просунуті оптимізації для більш широкого спектру випадків.
  • Працює у зв'язці з мовами на основі Truffle і дозволяє конвертувати Truffle AST у оптимізований нативний код завдяки часткового обчисленню. Часткове обчислення спеціалізованого інтерпретатора називається «перша проекція Футамуры» («first Futamura projection»). [Наскільки Я зрозумів з Вікіпедії, перша проекція Футамуры дозволяє підганяти інтерпретатор під вихідний код. Тобто, якщо в коді не використовуються деякі фічі мови, то ці фічі «випилюються» з інтерпретатора. А потім інтерпретатор «перетворюється» в компілятор, що генерує нативний код. Поправте, якщо Я наплутав.]
  • Містить розширені інструменти візуалізації, що дозволяють вивчати проміжне представлення компілятора на кожному етапі оптимізацій.
  • Написаний на Java. А значить його набагато простіше вивчати і експериментувати, ніж традиційні компілятори, написані на C або C++.
  • Починаючи з Java 9, Graal можна використовувати як JVM плагін.

Візуалізація IGV

З самого початку Graal проектувався як багатомовний компілятор. Але його оптимізації особливо добре підходять для мов високого рівня абстракції і динамізму. Java програми працюють в цій віртуальній машині так само, як в існуючих JVM компіляторах. В той час, як Scala програми працюють приблизно на 20% швидше. Ruby програми отримують приріст 400% в порівнянні з найкращої на сьогоднішній день реалізацією (і це не MRI).
Polyglot
Ще не втомилися? А адже це тільки початок.
Truffle містить спеціальний фреймворк для міжмовного взаємодії під назвою «Polyglot». Він дозволяє Truffle-мов викликати один до одного. А ще є Truffle Object Storage Model, яка стандартизує і оптимізує більшу частину поведінки об'єктів у динамічно типізованих мовах. А заодно дозволяє ділитися такими об'єктами. Не забувайте, що Graal і Truffle побудовані на технології Java і можуть спілкуватися з JVM мовами, такими як Java, Scala і навіть Kotlin. Причому це спілкування двостороннє: Graal-Truffle код може викликати Java бібліотеки, а Java код — викликати Graal-Truffle бібліотеки.
Polyglot працює дуже незвичайним способом. Як ви пам'ятаєте, Truffle представляє з себе фреймворк для опису вузлів абстрактного синтаксичного дерева (AST). В результаті звернення до інших мов відбувається не через додатковий шар абстракцій і обгорток, а шляхом злиття двох AST дерев в одне. У результаті отримане синтаксичне дерево компілюється (і оптимізується) всередині Graal як єдине ціле. А значить будь-які складнощі, які можуть з'явиться на стику двох мов програмування, можуть бути проаналізовані і спрощені.
З цієї причини дослідники вирішили реалізувати інтерпретатор C через Truffle. Зазвичай ми сприймає C як компільований мову. Але реальних причин для цього немає. Історія знає випадки, коли інтерпретатор C використовувався в реальних програмах. Наприклад відеоредактор Shake special effect давав користувачам можливість писати скрипти на C.
З-за відомих проблем з продуктивність скриптових мов, критичні ділянки програм часто переписують на C з використанням внутрішніх API інтерпретатора. У підсумку, такий підхід помітно ускладнює завдання прискорення мови, адже крім самого інтерпретатора необхідно запускати розширення на C. Завдання глобальної оптимізації ускладнюється із-за того, що ці розширення спираються на припущення про внутрішню реалізації мови.
Коли автори RubyTruffle зіткнулися з цією проблемою, вони прийшли до витонченого рішення: давайте напишемо спеціальний інтерпретатор C, який буде не тільки розуміти простий C, але також макроси та інші конструкції, специфічні для Ruby розширень. Тоді після злиття інтерпретаторів Ruby і C через Truffle вийде єдиний код, а витрати міжмовного спілкування будуть нівельовані оптимізатором. В результаті вийшов TriffleC.
Можете почитати прекрасне пояснення як все це працює в статті одного з дослідників цього проекту Кріса Сітона (Chris Seaton). Чи можете вивчити наукову статтю з детальним описом.
Давайте зробимо безпечне керування пам'яттю в C
Програми на C працюють швидко. Але є зворотний бік медалі — їх дуже люблять хакери, тому що занадто просто вистрілити собі в ногу під час роботи з пам'яттю.
Мова ManagedC з'явився як продовження TruffleC і замінює стандартне управління пам'яттю на контрольовані алокації зі збирачем сміття. ManagedC підтримує арифметику покажчиків та інші низькорівневі конструкції, при цьому уникаючи купи помилок. Ціна надійності — 15% просідання продуктивності порівняно з GCC, а також активне використання «невизначеного поведінки», яке так люблять багато компілятори C. Це означає, що якщо ваша програма працює з GCC, це не гарантує, що вона запуститься під ManagedC. При цьому сам ManagedC повністю реалізує стандарт C99.
Ви можете почерпнути більше інформації зі статті «Memory safe execution of C on a Java VM».
Налагодження і профілювання на халяву
Всі розробники мов програмування стикаються з проблемою нестачі якісних інструментів. Візьмемо для прикладу GoLang. Екосистема GoLang вже багато років страждає від поганих, примітивних і погано портируемых відладчика і профилировщика.
Ще одна проблема — додати підтримку відладчика у інтерпретатор. Тобто нативний код повинен бути близький до вихідного коду, щоб можна встановити взаємно однозначну відповідність між поточним станом віртуальної машини і того коду, що написав розробник. Зазвичай це призводить до відмови від оптимізацій компілятора і загального уповільнення налагодження.
І тут приходить на допомогу Truffle, який пропонує простий API, що дозволяє вбудувати просунутий відладчик у інтерпретатор… без уповільнення програми. Компілятор також застосовує оптимізації, а стан програми при налагодженні залишається таким, як того очікує програміст. Все завдяки метаданим, які генерують Graal і Truffle в процесі компіляції в нативний код. Отримані метадані використовуються для деоптимизации дільниць запущеної програми, щоб отримати початковий стан інтерпретатора. При використанні точки зупинки (breakpoint), точки спостереження (watchpoint), точки профілювання (profiling point) або іншого налагоджувального механізму віртуальна машина відкочує програму до її повільної формі, додає в AST ноди для реалізації потрібного функціоналу і перекомпилирует все назад в нативний код, який підміняється на льоту.
Звичайно відладчик це не тільки фіча середовища виконання. Потрібен ще UI для користувачів. Тому є плагін для NetBeans IDE з підтримкою будь-якого Truffle мови.
За подробицями відсилаю вас до статті «Building debuggers and other tools: we can have it all».
Підтримка LLVM
Truffle в основному використовується для створення інтерпретаторів вихідного коду. Але ніщо не заважає використовувати цей фреймворк для інших цілей. Проект Sulong — це Truffle інтерпретатор для байткода LLVM.
Проект Sulong поки перебуває на ранній стадії розробки і містить ряд обмежень. Але теоретично запуск байткода з допомогою Graal і Truffle дозволить працювати не тільки з C, але також C++, Objective-C, FORTRAN, Swift і можливо навіть Rust.
Sulong також містить простий C API для взаємодії з іншими Truffle мовами через Polyglot. Знову ж таки, завдяки незалежності від мови програмування і повністю динамічній природі даного API, отриманий код буде агресивно оптимізований, а накладні витрати будуть зведені до мінімуму.
Гаряча заміна (HotSwap)
Гаряча заміна — це можливість підмінити програмний код в процесі його роботи без перезавантаження. Це одне з головних переваг динамічних мов програмування, дозволяє домогтися високої продуктивності програмістів. наукова стаття присвячена реалізації гарячої заміни в фреймвороке Truffle (не впевнений, що підтримка вже додана в сам Truffle). Як і з відладчиком, профілювальником і оптимізаторами, розробникам мов на Truffle потрібно просто використовувати нові API для підтримки гарячої заміни. Цей API набагато зручніше ніж власні велосипеди.
Де підступ?
Як відомо, в житті немає нічого ідеального. Graal і Truffle пропонують рішення для майже всіх наших забаганок з початкового списку. Розробники мов програмування повинні бути просто щасливі. Але є ціна зручності такого:
  • Час прогріву
  • Споживання пам'яті
Процес конвертації інтерпретатора у оптимізований нативний код вимагає вивчення того, як програма працює в реальних умовах. Це, звичайно, не новина. Термін «прогрів» (тобто прискорення по мірі роботи) відомий всім, хто використовує сучасні віртуальні машини, такі як HotSpot або V8. Але Graal і тут рухає прогрес, виводячи оптимізації на основі профілювання на більш високий рівень. У підсумку GraalVM дуже сильно залежить від профилировочной інформації.
Саме з цієї причини дослідники заміряють тільки пікову продуктивність, тобто дають програмі попрацювати деякий час. При цьому не враховується час, необхідний на такий прогрів. Для серверних додатків це не становить великої проблеми, так як в цій області важлива лише пікова продуктивність. Але в інших додатках довгий прогрів може поставити хрест на використанні Graal. На практиці таку картину можна побачити при роботі з тестом продуктивності Octane (ви можете знайти його в технічному превью JDK): підсумковий бал трохи менше ніж у Chrome, навіть з урахуванням досить тривалого прогріву Graal (15-60 секунд), який не враховується в підсумковій оцінці.
Друга проблема: споживання пам'яті. Якщо програма сильно залежить від спекулятивних оптимізацій, їй необхідно зберігати таблиці з метаінформацією компілятора для деоптимизации — зворотного перетворення стану машини до абстрактного інтерпретатору. І займає ця метаінформація для стільки ж місця, скільки підсумковий код, навіть з урахуванням всіх ущільнень і стиснень. Не забувайте, що потрібно зберігати вихідне AST або байткод на випадок порушення евристичних припущень в нативному коді. Все це нещадно захаращує RAM.
Додайте до цього той факт, що Graal, Truffle і мови на основі Truffle самі написані на Java. А значить компілятору теж потрібно час на свій прогрів, щоб працювати в повну силу. І звичайно ж споживання пам'яті для базових структур даних буде рости, і ці базові структури компілятора потрапляють під збірку сміття.
Люди, які стоять за Graal і Truffle, звичайно в курсі цих проблем і вже обмірковують шляхи вирішення. Одне з них називається SubstrateVM. Це віртуальна машина цілком написана на Java і скомпільована заздалегідь (ahead-of-time) використовуючи відповідний компілятор Graal і Truffle. Функціонально SubstrateVM не така просунута як HotSpot: вона не може динамічно завантажувати код з інтернету, і збирач сміття у неї досить простий. Але виконавши компіляцію цієї віртуальної машини один раз можна сильно заощадити на часі прогріву в майбутньому.
І ще одна тонкість, про яку не можна промовчати. В нашому початковому списку хотілок є пункт про відкритість вихідного коду. Graal і Trulle — це великі і дуже дорогі проекти, написані досвідченими людьми. Тому їх розробка не може бути дешевою. На сьогоднішній день лише деякі частини з описаного поширюються з відкритими вихідними кодами.
Весь код можна знайти на GitHub або інших дзеркалах:
  • Graal & Truffle.
  • Розширювана версія HotSpot (основа проекту).
  • RubyTruffle
  • Sulong (підтримка LLVM bitcode)
  • Реалізації R, Python 3 і Lua (деякі з них створені як хобі/дослідні проекти).
А наступні частини не відносяться до OpenSource
  • TruffleC/ManagedC
  • TruffleJS/NodeJS API
  • SubstrateVM
  • Підтримка AOT
TruffleJS можна безкоштовно скачати в складі GraalVM release preview. Я не знаю, як можна погратися з TruffleC або ManagedC. Проект Sulong покривається частина цієї функціональності.
Що почитати
Канонічний, повномасштабний, все-в-одному туторіал по всьому Graal і Truffle в цій доповіді: «One VM to rule them all, one VM to bind them». Він йде три години, я вас попередив. Тільки для справжніх ентузіастів.
Є ще туторіали по використанню Truffle:
Що далі?
я згадав, що ми можемо прибрати бар'єри на шляху створення нових мов програмування. Це відкриє двері для нової хвилі інновацій в мовах. Ось невеликий список експериментальних мов. Сподіваюся, що цей список буде поповнюватися в майбутньому.
Якщо ви спробуєте нові ідеї, запропоновані в Graal і Truffle, то відкриєте для себе можливість реально працювати з власною мовою з перших днів його існування. Як результат, виникне зростаюче співтовариство учасників, які зможуть розгорнути свою мову у своїх проектах. Це породить магічний цикл: співтовариство залишає свої відгуки та ідеї, ви впроваджуєте поліпшення. В цілому це прискорить шлях від експериментів до кінцевого впровадження. Це як раз те, що я очікую.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.