Rubinius - ruby bytecode to exe

Fórumok

Sziasztok,

Lenne valakinek ötlete egy pár db iránymutató szóval, hogy merre induljak el rubinius témakörben, hogy a legyártott ruby bytecode-ot hogyan tudom lefordítani gépikódra?

Ugye pbx paranccsal egyszerűen csinál a ruby forrásból bytecode-ot. Azt sejtem, hogy ezt LLVM dolgozná fel, és az gyúrna belőle machine kódot. De hogyan kapcsolódik a kettő? LLVM tud ilyet alapból? Vagy rubinius biztosít LLVM-hez ruby bytecode interpreter plugin-t? Nem egészen világos a dolog.

Sajna rubinius dokumentációja is hagy kívánni való maga után.

Ötlet valakinek?

Hozzászólások

Akkor lehet én nem láttam csak erről infót.

Azt mondod, úgy működik, hogy minden futás előtt bytecode-ot, majd abból gépi kódot csinál? De amit teszteltem, ott speciel nem volt gyorsabb a futás idő. Igaz, nem volt megfelelő a teszt, ezért most megnézem megint.

De egyébként ha így van, akkor is lenne értelme az exe-t meghagyni szerintem, mégiscsak a fordítás is erőforrásba kerül. Meg pl. egy weboldal script-nél 1x kell fordítani, és sokszor futtatni, ezért nem tűnne ez logikusnak.

Valami nem oké, mert továbbra sem rövidebb a futási idő. Egyik tesztemnél tiszta ruby kódnál egy bonyolultabb algoritmussal 12 sec a futás, rubinius-szal 15 sec. Itt már azért jelentkezni kellene szerintem, vagy ennyi ideig tarthat a ruby kód lefordítása?

Arra sem találok választ, hogy a bytecode-ból ami .rbc kiterjesztésű, hogyan lehetne gépi kódot csinálni, vagy már azt futtatni megint.

Gépi kódban? Épp ez lenne a rubinius lényege is egyrészt (az mellett hogy egy ruby nyelv implementáció) - ahogy olvasom.

Ugye ha ruby-val futtatom, akkor simán interpretálja a ruby kódot az értelmező futás időben. Azért ehhez képest egy előre fordított és kicsit optimalizált gépi kódnál legalább fél nagyságrend sebesség növekedést várnék.

Minthogy a ruby egy teljesen dinamikus nyelv, nem tudom hogy akarod gepi kodra forditani forditasi idoben. Lehet, hogy a kod csak futasi idoben derul ki. Ime egy pelda:


class Valami
  def self.csinalj_fuggvenyt(kod)
    class_eval %(
      def fuggveny
        #{kod}
      end
    )
  end
end

Persze meg lehet hatarozni hogy mik azok a kodok amik nem fuggnek semmitol futasi idoben es azoknak a forditasat athozni forditasi idobe.

as ha a parameterben szereplo "kod" a stdin-rol erkezik futasi idoben? Forditasi idoben tfh a kod meg nem is letezik. Nyilvan a kod jelentos resze teljesen ismert forditasi idoben, es fordithato, de a ruby megengedi hogy futasi idoben szerkeszd ossze a kodot, tehat minden biztos nem lesz fordithato.

Nem. A ruby script-emet az interpreter dolgozza fel futás időben. Ekkor ez végrehajtódik. Erre kerestem lehetőséget, hogy hátha létezik olyan compiler, ami valami lebutított formában lefordítja a ruby kódot C-re, C++, C# vagy akármire, majd onnét gépi kódra (GCC-vel, whatever).

A rubinius projekt a leírása szerint a ruby kódból először egy saját bytecode-ot csinál, majd LLVM-mel gépi kódot. De semmi doksit nem találok a használatáról - igaz működik, csak éppen lassú.

Oke, sracok, eleg keso/koran van, de megprobalok tiszta vizet onteni a poharba.

Szoval, a Rubinius nem gepi kodot allit elo, hanem bytekodot. Ezzel gyakorlatilag az interpretalast sporolja meg, mert az rbc fajlban mar egy felepitett AST kerul szerializalasra, ha jol ertettem. Maganak a generalt kodnak mindenesetre az egvilagon semmi koze nincsen semmilyen gepi kodhoz, tekintve, hogy a Ruby amugy tenyleg egy dinamikus nyelv (lasd meg: python), amibol gepi kodot forditani nem egy egyszeru eset. Az egy LLVM bytecode, semmi tobb.
Egyebkent a Python pont jo pelda, ott is hasonlot oldottak meg. A Rubinius - amennyire en tudom - csak akkor forditja ujra az rbc kodot, ha a forrasaul szolgalo fajl valtozik (like python). Vagyis csak N futtatas eseten lesz lenyegi kulonbseg az eredmenyekben, az elso futtatas viszont nagyon lassu is lehet, foleg akkor, ha frissen telepitetted azt a gemet, amit az app hasznal, hiszen a rubygems keptelen az elozetes forditast telepiteskor elvegezni.

Ha jol emlekszem, maga az rbc fajl amugy kozvetlenul is vegrehajthato az rbx parancs segitsegevel, ekkor meg tovabbi idoket lehet nyerni, ha az adott script nagy terjedelmu. A Kernel#require mindenkeppen rbc fajlokat probal betolteni, es csak akkor failbackel a forditas+betoltes muveletre, ha ilyet nem talal, vagy talal, de az elavult.

EXE-t, ELF-et forditani kozvetlenul Ruby kodbol Rubinius-sal nem lehet. Azert raktak LLVM fole a cuccot, mert az LLVM egy hihetetlen gyors AST-keszito keretrendszert biztosit, gyorsabbat, mint ami az MRI-ben van. Illetve az LLVM lehetove teszi ezen AST szerializalasat es visszatolteset is anelkul, hogy ehhez tul sok erofeszitest kene tenni.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

Hát majdnem semmi nem igaz abból amit leírtál.

"Szoval, a Rubinius nem gepi kodot allit elo, hanem bytekodot."

Itt a saját leírásukat látod, vagyis:

Rubinius is software that translates the code for the Ruby programming language such as

puts "hello world"

and turns it into efficient machine code like this

push %rbp; mov %rsp, %rbp; push %rbx; subq $0x98, %rsp; cmp $0x0, 0x10(%rcx); call 0xffffffffff472010; jmp 0x9c; …

"Ha jol emlekszem, maga az rbc fajl amugy kozvetlenul is vegrehajthato az rbx parancs segitsegevel..."

Erre példát, mert nekem nem sikerült (sem az rbx-el, sem mást nem találtam a lefordított rubinius bin-ek között).

"EXE-t, ELF-et forditani kozvetlenul Ruby kodbol Rubinius-sal nem lehet."

Még egyszer leírtad, elvileg nem igaz rubinius-ék szerint, lásd feljebb.

Azert egy kis kulonbseg van a kozott, hogy a Rubinius mit csinal, es a kozott, hogy az rbc fajlba mi kerul. Azt mutasd mar meg nekem, hogy hol van az leirva, hogy az rbc fajlba gepi kod kerul. Mert amugy persze, az elkepzelheto, hogy a Rubinius gepi kodot allit elo es azt hajtja vegre, csak szerintem nagyon nem az kerul az rbc fajlba, foleg, hogy valahol emlekszem is, hogy olvastam, hogy az rbc fajlbol vissza lehet allitani a forrast, es ezt - mar ne is haragudj - gepi kodbol az eletben nem fogod megtenni. Nagyon ugyes es okos disassembler kell hozza.

Egyebkent ok maguk irjak le, mirol is van szo valojaban:

Rubinius is rather unusual as a Ruby implementation. It both compiles Ruby source code to bytecode and saves the compiled code to a cache so it does not need to recompile unless the source code changes. This can be great for utilities that are run often from the command line (including IRB). Rubinius merely reloads the cached file and runs the bytecode directly rather than needing to parse and compile the file. Sounds like a real win!

Forras

Az rbc fajlt mar nem tudom, hogy hogy kell futtatni, delutan megnezem neked.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

Az .rbc fájlba hozza létre a bytecode-ot. Engem nem ez érdekel. Nem az .rbc fájlba kerül gépi kód, ezt senki nem mondta. Olvasd el az idézetet amit feljebb írtam, ők printeltek hozzá gépi kódot is, az az fölötti részt.

Azt szeretném tudni, hogy mivel leírásuk szerint on-the-fly gépi kódot gyártanak a ruby byte-code-ból, hogyan lehetne a gépi kódot megtartani.

A normális relatív, egyébként nem, másrészt igen. Nem, mert gyorsabban is futhatna és igen, mert nem tartják annyian karban a kódot és nem optimalizálják annyian, mint a hivatalos interpretert. Gondolom a hivatalos folyamatosan gyorsul, mint ahogy azt a többi hasonló nyelvnél már megszokhattuk, csak ki gyorsabban, ki lassabban

Ez kodfuggo. A helloworld cimu kodok nem fognak gyorsabbak lenni, a szamolasigenyesek szerintem nem fognak szamottevoen gyorsulni. Egyreszt inkabb arrol van szo, hogy a bootolas tud gyorsabb lenni, masreszrol pedig a dinamikus kodok tudnak sztem gyorsabbak lenni. De ezt mar viszont nem tudom.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

Nem olvastam el, mit csinál ez a rubinius, de nem inkább arról van szó, hogy a parseolást próbálják megspórolni? Vagyis ez a JIT nem olyan, mint amit pl. egy jvm-ben találunk, ami a bytecode-ból állít elő az adott környezetre optimalizált gépi kódot, hanem inkább olyan, mint a javac, ami a virtuális gépnek egy emészthetőbb bytekódot állít elő a forrásból, de gyakorlatilag semmit nem optimalizál.

Ez egy kicsit turmix. Elso korben bytekodot allit elo, ezt cacheli le az rbc fajlba, masodszor pedig a bytekodbol allit elo optimalizalt gepi kodot. Nyilvan ha mar van eloforditott bytekod, akkor az elso lepes kiugrik.

Mondom, pont olyan, mint a python interpreter, ha kered, az is csinal neked bytekodot.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

nem, csak valaki hülyeséget beszél.
Az hogy az konkrétan machine code, egy dolog.
A bytecode továbbra is bytecode marad (mégha mixed bytecode is machine code-al)
Kicsit kell csak lentebb görgetni, hogy lásd, hogy működik, egy machine code végrehajtásához nem kell:
"Bytecode Compiler" sem "VM"
De ezt még ők maguk is leírják
"The Rubinius bytecode virtual machine is written in C++, incorporating LLVM to compile bytecode to machine code at runtime. The bytecode compiler and vast majority of the core classes are written in pure Ruby.

To interact with the rest of the system, the VM provides primitives which can be attached to methods and invoked. Additionally, FFI provides a direct call path to most C functions. "
Innen indulva, pusztuljon aki nem tud értelmezni egy nagy szines-szagos táblázatos leírást

Nem értem hogy miért próbálod erőltetni, hogy nincs jelen gépi kód, csak csupán ruby-t vagy annak bytecode-ját interpretálják. Vagyis írod, de úgy próbálod beállítani, hogy a futás időben lefordított gépi kód az nem is az, hanem akármi más.

Szerintem gépi kód részek futnak. Lehet hogy a bytecode utasítás foszlányaira külön gépi kód rutin-ok vannak és nem is annyira optimális, de ezt nem érdemes megmagyarázni.

btami linkelte a wikit a JIT-re, itt ezt írja:

"Interpreted code is translated from a high-level language to a machine code continuously during every execution, whereas statically compiled code is translated into machine code before execution, and only requires this translation once."

Ennek fényében szerintem nem igazán volt hülyeség arra gondolni, hogy a lineárisan végrehajtott utasítás lépések rögzíthetők és visszajátszhatók - hogy ekkor egy nem igazán optimalizált exe-t tudjak futtatni.

Az már hab, hogy miért találom sokkal lassabbnak. Mindegy, írtam a levlistájukra, ami elég halott, de azért remélem válaszolnak majd.

"Szerintem gépi kód részek futnak."

Kimondtad a kulcsszot. Szerinted. A Rubinius fejlesztoi szerint meg nem.

Egyebkent csatlakozom, sztem se fogod kinyerni a JIT-tol az aktualisan futtatott machine kodot, mert azt mar altalaban internal API-kent valositjak meg, illetve teljesen maskepp kell hozzafogni egy exe forditashoz, mint egy JIT-hez. Lehetnek olyan utasitasok a kododban, amik sosem futnak le, igy a JIT sosem fog hozzajuk machine kodot generalni neked, tehat az optimalizalatlansag a kisebbik problema lenne az igy kinyert koddal (ha ki lehetne nyerni). Ha egy dontesi agnak elore lathato, hogy csak a true aga fog lefutni, a false-hoz sosem fogsz machine kodot kapni, mert a JIT lenyege, hogy runtime talalja ki, hogy ide mi jonne. Funkcionalisan hianyos kodot nyernel ki, ennyi erovel a bytekodhoz is adhatsz elf fejlecet, pont annyi ertelme lenne.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

Az eredeti kodhoz kepest igen.



def foo
  puts "foo"
end

def bar
  puts "bar"
end

if ARGV.first and ARGV.first == 'foo'
  foo
else
  bar
end

Innentol a bemeneti parameter fuggvenye, hogy mibol lesz gepi kod. Amennyiben 'foo' -val hivod meg a fenti scriptet, akkor a foo fog lefordulni, viszont a bar nem (hiszen azt nem hivja senki), ha barmi massal, akkor pedig a bar fog lefordulni, es a foo nem, hiszen ebben az esetben megint nincs miert vele foglalkozni.
Tehat, a bytekod generalas az mindket fuggvenyre meglesz, viszont a gepi kod csak az egyikre, magyarul az esetlegesen kinyerheto gepi kod funkcionalisan hianyos lesz, mert hiszen ha te egy exe-t akarsz a vegen forditani ebbol, akkor nem mondhatod azt, hogy marpedig ezt az exe-t csak "foo"-val vagy csak "bar"-ral lehet hivni, hiszen ez esetben gyakorlatilag bedrotozod az argumentumot, az elkeszult exe fajl ugyanis csak azt az argumentumot kepes feldolgozni, amit legeloszor kapott, a masikra hianyzik a funkcio.

Elkepzelheto olyan eset, amikor egyaltalan nincsenek dontesek a kodban (huhh, conditionalok nelkuli kod, nem egy egyszeru eset), viszont erre compilert nem lehet irni, egyaltalaban, csak altalanos compilert lehet irni, amiben semmifele JIT szeru mukodes nem lehetseges. Ebbol kovetkezoen barmi, ami a JIT-bol kijon, az minden, csak nem hasznalhato. Az csak egy linearis kod, minden dontesi esetnel csak egyetlenegy agat figyelembeveve.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

Olyan compilert, ami egy JIT-nek a veret szivja, olyat semmikepp se lehet irni. Persze, lehet compilert irni Rubyhoz, a JRuby konkretan megcsinalta (khmm... na mindegy), bemenet ruby fajl, kimenet java class. Neki lehet szaladni gepikoddal is, de mondom, olyat, hogy egy JIT-bol kiszivattyuzni a kodot, es betolni egy exebe, na olyat nem lehet csinalni, mert a kimeno exe a forrasfajlra nezve funkcionalisan hianyos lesz.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

Még szerencse, különben nem lehetne futtatni. Gyakorlatilag ha úgy nézzük, akkor az interpreter is fordít, mert ugye a processzor a gépi kódot hajtja végre.

Én úgy értettem amit mondtam, hogy nem lehet mindent egyben előre lefordítani, tehát egy olyan binárist készíteni, amit utólag semmivel nem kell kiegészíteni a program bármely lehetséges lefuttatásához.
Persze ebbe is bele lehetne kötni, mert végülis egy JIT fordító pont azt csinálja, hogy az ő futása közben fordít le más programot és futtatja azt, szóval nem lehet ezt ennyire pontosan definiálni, de azért legtöbb esetre érvényes lehet.

szerk.: Bocs, az előre szó felett átsiklottam. Nem ismerem ezt a funkciót, mindenesetre egy igazi dinamikus nyelvben írt programban azért nagyon sok minden futási időben dől el, akár az is, hogy egy osztálynak mi az őse. Azért ettől még elég messze van a C# dynamic része.

NGen.exe. És szerintem mindent le lehet fordítani előre gépi kódra, akármennyire is dinamikus. Ezt csak érzésre mondom, aki ért a Turing-elmélethez, az tudja bizonyítani is, vagy esetleg cáfolni. (Ja, pl. fogod a dinamikus nyelv forráskódját, összegyúrod egy interpreterrel, és így kiadsz egy exe-t, ami ezt az egy forráskódot tudja futtatni. Máris megvan teljesen előre fordított dinamikus nyelv :). Igaz, az eredeti scriptet adatnak használja, de a kód az gépi. Ezen már csak gyorsítani kell.) Az is lehet, hogy nem optimális mindent előre lefordítani, de sok az okos mérnök, és mindig minden fejlődik :). De akár a Rubinius is mondhatja azt 1-1 esetre, hogy throw new NotSupportedException. Lásd még Facebook HipHop. Ha egy változóba teszel egy intet, utána egy osztályt, aztán egy függvény pointert, az biztos szépen elfut egy dinamikus script nyelvben, de hogy nem jó design, az is biztos.

--
joco voltam szevasz

Azert egy leforditott kod ne mondja mar az hogy UnsupportedException occurred, mert akkor nagyon sikoltozni kezdek.
Illetve persze, lehet az intepretert, a core frameworkot meg a forrast egybegyurni, csak azt ne akarjuk elore forditasnak hivni, mert akkor a py2exe is gepi kodot allitana elo (pedig nem is).

Egyebkent sajnos/szerencsere a ruby van annyira dinamikus, hogy menet kozben lehet forraskodbol szulni fuggvenyeket, osztalyokat, modulokat. Az eval-nak viszonylag nagy tere van. Ha ezt gepi kodra kell leforditani, akkor vagy kell az exe-be csomagolni interpretert is (JIT-tel, mindennel), vagy eleg bonyolult szkenariok adodnak ennek a feldolgozasara.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal