Spustit jako prezentaci

Alternativní implementace Ruby

MRI, YARV, JRuby, XRuby, IronRuby, Ruby.NET, Rubinius, Cardinal

David Majda

3. 10. 2007, Praha

  • Nejsem railsař, zajímá mě především Ruby jako takové. Proto byť je tato prezentace přednášena na setkání uživatelů Ruby on Rails, samotných Rails se prakticky netýká.
  • Přednáška je jen přehledová, určená pro publikum, které nemá zkušenosti s psaním interpreterů či kompilátorů, a tedy ne až tak technická. Detaily lze najít na stránkách odkazovaných v poznámkách.

Osnova

  1. Představení MRI
  2. Představení alternativních implementací
  3. Zhodnocení a budoucnost

MRI (Matz Ruby Interpreter)

K jednotlivým problémům:

Naivní interpretace
MRI funguje tak, že jeho parser zdrojový kód převede do AST – stromu, který zachycuje strukturu použitých konstrukcí. Vyhodnocování skriptu pak probíhá "pobíháním" po tomto stromě a vyhodnocováním jeho větví. To je značně pomalé. Většina jazyků (Python, PHP, JavaScript,...) strom dále zpracovává a výsledkem je zkompilovaný bytecode, řetězec relativně nízkoúrovňových instrukcí virtuálního stroje jazyka. Jejich následné spouštění je podstatně rychlejší než práce se stromem.
Vlákna
MRI používá tzv. green threads, tj. vlákna implemenetovaná nikoliv operačním systémem, ale přímo programem v user-space. Práce s těmito vlákny je sice o něco rychlejší, než s klasickými vlákny, ale mají některá omezení (např. blokující systémové volání zablokuje všechna vlákna) a způsobují problémy s kompatibilitou u implementací jako JRuby nebo IronRuby, kde není možné green threads použít kvůli omezením použité platformy (zde JVM resp. .NET). Matz se pro green threads rozhodl na počátku vývoje – v době, kdy ještě mnohé operační systémy spolehlivě fungující podporu vláken neměly. Tehdy to bylo správné rozhodnutí, zvýšilo portabilitu, ale v dnešních podmínkách jeho negativa převažují nad pozitivy.
Unicode
V Ruby jsou řetězce prosté posloupnosti 8-bitových hodnot. Jakékoliv záležitosti okolo kódování si musí programátor řešit sám. Je to poněkud překvapivý přístup vzhledem k tomu, že Ruby pochází z Japonska, kde jsou jistě s kódováním mnohem větší potíže, než třeba u nás. Pro pořádek je nutno dodat, že samotný zdrojový kód lze psát v 7-bitovém ASCII, pomocí Kanji (v kódování EUC nebo SJIS), nebo v UTF-8. Informace o kódování zdrojového textu se interpretu Ruby sděluje pomocí parametru -K.
Embedding
MRI je napsán tak, že ho lze začlenit do aplikace a používat v ní Ruby jako vestavěný skriptovací jazyk. Bohužel ale spousta datových struktur v MRI je globálních, a tak není možné mít v aplikaci spuštěno více interpreterů Ruby zároveň (podobně, jako má například každá webová stránka v prohlížeči svůj kontext pro skripty v JavaScriptu). To využití Ruby jakožto skriptovacího jazyka pro aplikace značně omezuje, až znemožňuje.
Kvalita kódu
Zanoříte-li se do kódu MRI, zjistíte, že spousta věcí je v něm udělaná jednoduše špatně. Nevhodné názvy proměnných a funkcí, spousta globálních proměnných, duplicity v kódu, nevhodně strukturovaná logika, prakticky žádné komentáře...

Proč další implementace?

O jednotlivých fázích, kterými vývoj jazyka obvykle prochází, hezky píše Ola Bini.

Seznam alternativních implementací

YARV

Pro někoho může být zajímavý přehled instrukcí YARV.

Informace o zrychlení pocházejí z tohoto testu.

Vývoj YARV probíhal původně odděleně od hlavní vývojové větve Ruby, ke sloučení došlo přelomu let 2006/2007.

Koichi Sasada na rozdíl od Matze umí dobře programovat, na kódu je to vidět.

JRuby

Podpora Sunu má konkrétní podobu, a to že Sun zaměstnává dva z core vývojářů.

"Skoro 100%" znamená, že jsou jisté rozdíly mezi JRuby a MRI. Chybí například podpora continuations (které na JVM nejde efektivně implementovat), je využíván Javovský engine pro regulární výrazy (který se v detailech odlišuje od enginu v MRI) a vlákna jsou implementována pomocí standardních Javovských (jinak to v JVM nelze udělat).

Ukázka integrace

Java z Ruby

include Java

frame = javax.swing.JFrame.new("Window")
label = javax.swing.JLabel.new("Hello")

frame.getContentPane.add(label)
frame.setDefaultCloseOperation(
  javax.swing.JFrame::EXIT_ON_CLOSE
)
frame.pack
frame.setVisible(true)

Ruby z Javy

import org.jruby.*;

Ruby runtime = Ruby.getDefaultInstance();
runtime.evalScript("puts 1 + 2");

XRuby

IronRuby

Ruby.NET

Autoři zatím nechtějí implementovat některé funkce jako vlákna či continuations, protože není jasné, zda a v jaké podobě budou v budoucích verzích Ruby.

Integrace je zatím trochu omezená, například z jiných jazyků lze používat jen třídy v Ruby, které jsou zděděné přímo od Object.

Na stránkách projektu se píše, že vývoj bude otevřen v druhé polovině 2007.

Vztah s IronRuby je docela zajímavý: Microsoft mimo jiné licencoval parser Ruby z Ruby.NET a používá ho v IronRuby.

Rubinius

Zajímavost: Evanu Phoenixovi, autorovi Rubiniusu, se jádro v C moc nelíbí, v budoucnu ho chce generovat z jakéhosi dialektu Ruby.

K jednotlivým cílům:

Multithreading
Cílem je hlavně náprava situace s embeddingem MRI. Momentálně tento cíl není splněn kvůli použití některých komponent MRI.
Čistý a čitelný kód
Netřeba komentovat :-)
Spolehlivost
Na kontrolu céčkového kódu se používá Valgrind.
Umožnění experimentů
Mělo by například být možné vyměnit použitý optimalizátor kódu nebo garbage collector.

Otevřený model vývoje znamená, že každý, kdo pošle alespoň jeden patch, získává commit access k vývojovému stromu. Tomu říkám důvěra.

Cardinal

Projekt Parrot si klade za cíl vytvořit univerzální virtuální stroj umožňující efektivní běh dynamických jazyků. V současnosti obsahuje front-endy k mnoha různorodým jazykům, včetně PHP, Pythonu, Perlu, Luy, JavaScriptu, Scheme a – prostřednictvím projektu Cardinal – i k Ruby.

Front-end pro daný jazyk vždy zkompiluje zdrojový kód do PIR (též IMC), což je relativně high-level mezijazyk pracující s pojmy jako funkce nebo lokální proměnné. V serializované podobě je PIR čitelný pro člověka a kód v něm je podobný kódu v původním jazyce.

V další fázi je PIR přeložen do nízkoúrovňového bytecode, PBC. Tento bytecode je pak spouštěn virtuálním strojem Parrotu a případně pro efektivitu JIT překládán do nativního kódu dané platformy.

Praxe, aneb "běží na tom Railsy?"

Budoucnost

O tom, zda Rubinius postupně nenahradí MRI jako oficiální implementaci, se rozepisuje Ola Bini. Osobně bych jeho šance viděl docela dobře, především díky mnohem jednoduššímu a čistěji napsanému zdrojovému kódu.

JRuby má silnou pozici především díky své relativní vyzrálosti a podpoře Sunu.

U IronRuby je na místě otazník ze dvou důvodů: V současnosti není příliš vyzrálé a pak je od Microsoftu. Microsoft se zde pohybuje na neobvyklém poli a je otázka, jak se v budoucnu zachová (vzhledem k jeho vztahům k open source na jiných frontách).

Zajímavá je otázka, zda (nebo spíš kdy) se bude některá z alternativních implementací cítit natolik silná, aby začala definovat vlastní rozšíření jazyka či některé věci řešit záměrně nekompatibilně (modulo technická omezení, jako vlákna v JRuby). Dojde k fragmentaci a divergenci jednotlivých implementací Ruby, jako se to stalo u jiných jazyků (např. C)?

Zajímavosti

Příkladem kopírování kódu je parser Ruby. Prakticky všechny implementace používají původní Matzův parser přepsaný do cílového jazyka a případně mírně upravený (jedinou výjimkou je projekt XRuby). Důsledkem je, že Matzův ošklivý a obtížně udržovatelný kód se šíří jako virus.

Jaké že ošklivosti se to skrývají pod povrchem Ruby? Osobně se mi nelíbí bloky, které jsou "podivné" tím, že to nejsou obyčejné anonymní funkce/metody. Spousta pravidel (například pro lookup konstant) je dost komplikovaných. Toplevel kontext není podobný ani kontextu třídy, ani kontextu metody – je to takový kočkopes. Matz prováděl také spoustu mikrooptimalizací, které ale mají vliv na sémantiku – velké ne-e pro každého poctivého návrháře a implementátora jazyka, který o sobě chce tvrdit, že je elegantní. Ruby je elegantní jen do doby, než se podíváte pod kapotu. Bohužel.

Konec

Dotazy?