Archivio per la categoria "IronRuby"



IronRuby sempre più vicino a Ruby on Rails

Se non è in grado di far girare Ruby on Rails, allora non è Ruby... più o meno con queste parole John Lam, agli inizi dello sviluppo di IronRuby, aveva sottolineato la volontà di ottenere un'implementazione del linguaggio che fosse il più fedele possibile all'MRI (l'interprete originale di Matz). Oggi, con questo suo tweet, arriva la prima dimostrazione pratica di come questo obiettivo sia ormai sempre più vicino. Ovviamente manca ancora parecchio lavoro per completare IronRuby e attualmente il team è dedicato interamente a implementare e sistemare tutte le parti necessarie a poter effettuare l'hosting di una semplice applicazione RoR giusto in tempo per la RailsConf 2008 che avrà inizio tra pochi giorni, ma il ritmo sembra ormai molto buono. E' interessante far notare che negli ultimi due mesi il lavoro del team di IronRuby si è fatto molto più trasparente in seguito a una forte critica sviluppatasi direttamente sulla mailing list, in cui si faceva notare come l'assenza di aggiornamenti sullo stato dei lavori e i commit in SVN abbastanza rari rendevano il lavoro di contribuzione e di testing molto difficile. La discussione ha generato un dialogo che ha portato a commit decisamente più frequenti, post dei diff sulla mailing list e review del codice e dei nuovi sviluppi... e la comunità che si sta formando intorno ad IronRuby ha apprezzato. Personalmente in queste ultime due settimane di silenzio sul blog ho avuto modo, tra le varie attività, di approfondire più seriamente gli internals di IronRuby provando a implementare qualche funzionalità mancante e devo ammettere che il suo codice e la struttura generale del progetto si sono dimostrati dannatamente ottimi e relativamente facili da capire nonostante non sia ancora esistente documentazione pubblica in merito (sarebbe stata inutile dal momento che il core ha cominciato a stabilizzarsi solo di recente). Se nel frattempo io mi sono divertito con metodi semplici come Kernel#rand e Kernel#srand per poi passare a Kernel#system con relativa classe Process::Status per cominciare a prendere familiarità con il progetto, qualcuno si è spinto ben oltre iniziando a ricreare un'implementazione di Shoes su IronRuby basandosi sulle API .NET di Windows Forms... potremmo chiamarlo IronShoes? Insomma ad oggi sono fortemente ottimista riguardo al futuro di IronRuby soprattutto visti gli ultimi sviluppi. Sono ottimista anche per la situazione di Ruby in generale perché stanno arrivando segnali molto interessanti dalla comunità, ma preferisco parlare di questo in un post successivo.

Microbenchmark: Ruby 1.9.0 contro tutti (... non proprio...)

Il rilascio di Ruby 1.9 è previsto intorno a Natale e con il passare dei giorni la curiosità dei rubyisti si fa sempre più intensa. Stuzzicato da un microbenchmark pubblicato da Antonio Cangiano sul suo blog in cui ha messo a confronto la velocità di esecuzione di Ruby 1.8.6, Ruby 1.9.0 e Python 2.5.1 usando volutamente una funzione ricorsiva che calcolasse i primi 35 numeri della successione di Fibonacci, ho pensato che sarebbe stato interessante fare lo stesso test con i suoi script ma con condizioni diverse. A differenza di Antonio, che ha eseguito il suddetto microbenchmark sul suo MacBook Pro con OS X 10.5, ho pensato di effettuare i miei test su Windows chiamando in causa a questo punto anche IronRuby e, tanto per fare, anche PHP e Lua. Mi sarebbe piaciuto provare anche JRuby, ma purtroppo non ho nessuna esperienza e non saprei nemmeno da dove cominciare.

  • Configurazione: Microsoft Windows XP SP2 (x86) - AMD Athlon64 3700+ (2,2 GHz) - 2 GB RAM
  • Dettaglio versioni:
    • Ruby 1.9.0 (SVN rev. 14067 compilata con VC++ 2005)
      ruby 1.9.0 (2007-12-01 patchlevel 0) [i386-mswin32_80]
    • Ruby 1.8.6 (Ruby OneClick Installer 1.8.6-25)
      ruby 1.8.6 (2007-03-13 patchlevel 0) [i386-mswin32]
    • IronRuby 1.0 pre-alpha (SVN rev. 65 compilata con CSC di VS 2005)
      IronRuby Pre-Alpha (1.0.0.0) on .NET 2.0.50727.832
    • Python 2.5.1 (Standard Python 2.5.1 Windows Installer)
      Python 2.5.1 (r251:54863, Apr 18 2007, 08:51:08) [MSC v.1310 32 bit (Intel)]
    • Python 3.0a1 (Standard Python 3.0a1 Windows Installer)
      Python 3.0a1 (py3k:57844, Aug 31 2007, 16:54:27) [MSC v.1310 32 bit (Intel)]
    • PHP 5.2.5 (Binari ufficiali per Win32 - versione TS - no debug - Zend Engine 2.2.0)
      PHP 5.2.5 (cli) (built: Nov  8 2007 23:18:51)
    • PHP 5.3.0 (Snapshot ufficiale - versione TS - no debug - Zend Engine 2.3.0)
      PHP 5.3.0-dev (cli) (built: Dec  1 2007 04:19:04)
    • PHP 6.0.0 (Snapshot ufficiale - versione TS - no debug - Zend Engine 3.0.0-dev)
      PHP 6.0.0-dev (cli) (built: Dec  1 2007 02:04:58)
    • Lua 5.1.2 (LuaBinaries per Win32 di LuaForge)
      Lua 5.1.2  Copyright (C) 1994-2007 Lua.org, PUC-Rio

Le build di Ruby 1.9 e IronRuby sono state effettuate direttamente da me allineandomi questa mattina con i trunk nei rispettivi repository SVN, nel primo caso senza badare a ottimizzazioni particolari e nel secondo caso usando la modalità release. Per le versioni di sviluppo di PHP ho prelevato questa mattina da snaps.php.net i binari ufficiali e ho effettuato i test con la configurazione (php.ini) di default, così come per la versione stabile 5.2.5. Lo script per Python è stato leggermente modificato per la versione 3.0 dal momento che print è diventata una funzione e funziona diversamente dalla versione 2.5.

Gli script usati per Ruby, IronRuby e Python sono gli stessi di Antonio, aggiungo solo le mie varianti Lua e PHP (occhio, si tratta solamente di un becero port ai rispettivi linguaggi). Ecco come si presenta la versione in Lua:

function fib(n) if n == 0 or n == 1 then return n end return fib(n - 1) + fib(n - 2) end for n = 0, 35 do print("n=" .. n .. " => " .. fib(n)) end

Ed ecco invece la versione in PHP:

function fib($n) { if ($n == 0 || $n == 1) return $n; return fib($n - 1) + fib($n - 2); } for ($i = 0; $i <= 35; $i++) echo "n=", $i, " => ", fib($i), "\n";

I tempi riportati di seguito rappresentano la media calcolata sui risultati di 6 esecuzioni scartando la prima. Ecco i risultati ottenuti:

Ruby 1.9.0 : 21,123s
Ruby 1.8.6 : 94,736s
IronRuby : 26,123s
Python 2.5.1 : 33,827s
Python 3.0a1 : 48,718s
PHP 5.2.5 : 91,672s
PHP 5.3.0 : 77,813s
PHP 6.0.0 : 73,969s
Lua 5.1.2 : 14,912s

Ora un po' di considerazioni:

  • Ruby 1.9 è dannatamente più veloce di Ruby 1.8.6. Punto.
  • IronRuby non è nemmeno uscito dallo stato di pre-alpha e già offre prestazioni molto buone, vicine a quelle di Ruby 1.9 che invece si sta avvicinando al rilascio. Ottimo!
  • Mi incuriosisce la differenza tra i tempi di Ruby 1.8.6 registrati da Antonio su OS X e quelli registrati da me su Windows: 159 secondi rispetto a 95 secondi mi sembra un abisso, soprattutto perché immagino che un Core 2 Duo 2.2 GHz sia più performante di un Athlon64 3700+ 2.2GHz.
  • Python 3.0 è più lento di Python 2.5.1, ma la versione 3.0a1 risale a fine Agosto per cui sarebbe il caso di ripetere lo stesso benchmark con una versione più aggiornata.
  • PHP sembra migliorare abbastanza con il passaggio da Zend Engine da 2.2.0 a 2.3.0, ma comunque le prende alla grande da Ruby 1.9 e Python.
  • Lua li straccia senza alcun ritegno.
  • Ordine di arrivo: Lua 5.1.2, Ruby 1.9.0, IronRuby 1.0 pre-alpha, Python 2.5.1, Python 3.0a1, PHP 6.0, PHP 5.3, PHP 5.2.5, Ruby 1.8.6.

Ovviamente un singolo microbenchmark di questo tipo non può essere assolutamente considerato come un valido indicatore delle prestazioni offerte da un linguaggio in situazioni reali, però i risultati sono comunque curiosi. A quanto pare i rubyisti avranno già un bel regalo assicurato per questo Natale 🙂

PS: noto al termine della scrittura che Antonio ha scritto un altro articolo, aggiungendo Lisp e Haskell.

Estendere le classi builtin di IronRuby con nuovi metodi

In precedenza abbiamo visto come con IronRuby sia già possibile estendere a runtime le classi builtin sfruttando lo stesso Ruby e il suo concetto di classi aperte, tuttavia molti metodi che noteremo mancare in queste prime fasi di sviluppo, come MutableString#reverse usato per il nostro esempio ma anche molti altri , fanno parte del set di metodi "standard" delle classi builtin dell'interprete Ruby originale di Matz. L'ideale sarebbe quindi implementarli direttamente in IronRuby, guadagnandone anche in prestazioni, ma la sorpresa è che questo lavoro non è per nulla complicato.

Muoversi all'interno dell'archivio con i sorgenti è semplice, nella directory Docs è presente un readme.htm (tra l'altro realizzato con TiddlyWiki) con alcune indicazioni sulle linee guida per la scrittura del codice (SourceCodeLayout) e un paragrafo che fa al caso nostro: come aggiungere un metodo a una classe builtin (AddAMethod). In Src abbiamo la directory eMicrosoft.Scripting e Ruby, rispettivamente i sorgenti del DLR e di IronRuby. I file su cui andremo a lavorare sono contenuti in Src\Ruby\Builtins dove si trovano le implementazioni di tutte le classi builtin di IronRuby.

A questo punto apriamo il file Src\Ruby\Builtins\MutableString.cs, scriviamo il nostro metodo all'interno della classe MutableString e vediamo di commentarlo:

1 [RubyMethodAttribute("reverse", RubyMethodAttributes.PublicInstance)] 2 public static MutableString Reverse(MutableString str) { 3 if (str.Length <= 1) 4 return new MutableString(str); 5 6 char[] reversed = new char[str.Length]; 7 int len = str.Length - 1; 8 for (int i = 0; i <= len; i++) 9 reversed[i] = str[len - i]; 10 11 return new MutableString(new String(reversed)); 12 }
  • riga 1: per specificare il nome che verrà usato in Ruby per il metodo e la sua visibilità sono stati creati rispettivamente l'attributo RubyMethodAttribute e l'enum RubyMethodAttributes. Come spiegato nel documento allegato, il motivo per cui viene utilizzato RubyMethodAttribute è semplicemente dovuto al fatto che in Ruby possono esistere nomi di metodi non validi in C# come empy? oppure downcase!, inoltre in questo modo è possibile ricreare la moltitudine di operatori come <=> (comparazione) oppure =~ (corrispondenza).
  • riga 2: i metodi che andremo a creare sono statici poiché in realtà il DLR sfrutta una propria implementazione degli extension method di C# 3.0, in cui i metodi di estensione si presentano appunto come statici e il primo argomento è rappresentato da this per i metodi di istanza. In questo caso, il primo argomento sarà rappresentato dall'oggetto chiamante (è come se da Ruby arrivasse self).

Commentiamo rapidamente l'implementazione, saltando solo la parte che si occupa semplicemente di invertire la stringa:

  • riga 3-4: se la stringa è vuota oppure è formata da un solo carattere allora è inutile proseguire dal momento che non c'è molto da invertire, tuttavia ritorniamo ugualmente una nuova istanza di MutableString poiché questo è il comportamento di Ruby, basta osservare in IRB con l'interprete originale come ogni chiamata a String#reverse restituisca un oggetto con un object_id sempre differente:
    irb(main):001:0> "CIAO".reverse.object_id
    => 23652180
    irb(main):002:0> "CIAO".reverse.object_id
    => 23648140
    irb(main):003:0> "C".reverse.object_id
    => 23644340
    irb(main):004:0> "C".reverse.object_id
    => 23640540
    irb(main):005:0> "".reverse.object_id
    => 23636820
    irb(main):006:0> "".reverse.object_id
    => 23633100
  • riga 11: non ci sono costruttori di MutableString che prevedano un array di caratteri come parametro, per cui creiamo una istanza intermedia di String partendo dal nostro array di caratteri e creiamo da quella la nostra nuova istanza di MutableString. Avremmo potuto fare in altro modo ma facciamoci andar bene questa riga, soffermarsi a questo punto dei lavori sui dettagli implementativi per spaccare il byte è abbastanza inutile visto che molte parti saranno soggette a cambiamenti nel corso dello sviluppo di IronRuby e del DLR.

Per compilare il tutto correttamente in base alle nostre modifiche, seguiamo alcuni passi:

  1. Modificare il file Build.cmd cambiando la configurazione da modalità Release a Debug ( /p:Configuration=Debug ) e poi modificare il file Src\Ruby\Builtins\GenerateInitializers.cmd togliendo il ..\ iniziale (c'è un errore nei path).
  2. Eseguire Build.cmd. Questa prima compilazione genererà l'utility Bin\Debug\ClassInitGenerator.exe che lanceremo attraverso Src\Ruby\Builtins\GenerateInitializers.cmd e che (ri)genera automaticamente il file Src\Ruby\Builtins\Initializer.Generated.cs contenente i vari dispatcher ai metodi. Ogni volta che a una classe builtin aggiungiamo un metodo visibile da Ruby, dobbiamo rigenerare questo file (si può anche editare a mano, ma è ovviamente meno comodo e aumenta la possibilità di errore).
  3. Lanciare nuovamente Build.cmd per compilare il progetto con il nostro Initializer.Generated.cs aggiornato per riflettere l'aggiunta del metodo MutableString#reverse.

Non ci resta da fare altro che verificare i risultati tramite RBX, lanciandolo da Bin\Debug\rbx.exe:

>>> "CIAO".reverse
=> "OAIC"
>>> "CIAO".reverse.object_id
=> 43
>>> "CIAO".reverse.object_id
=> 44
>>> "C".reverse.object_id
=> 45
>>> "C".reverse.object_id
=> 46
>>> "".reverse.object_id
=> 47
>>> "".reverse.object_id
=> 48

La stringa viene correttamente invertita e il comportamento è il medesimo di quello riscontrato in IRB con l'interprete Ruby originale.

Impressioni e primi passi con IronRuby

Ormai si sta parlando in diversi lidi del rilascio dei sorgenti di una prima versione "parecchio alfa" (non per niente definita pre-alfa 1) di IronRuby tuttavia non c'è niente di meglio che esplorare e sperimentare in prima persona sia le potenzialità che il codice sorgente stesso di questa nuova implementazione di Ruby totalmente basata su .NET Framework. Trattandosi di una versione ampiamente preliminare è ovvio che non tutto funzioni come dovrebbe così come è ovvio che manchino molti pezzi, però personalmente trovo che il progetto sia già in uno stato relativamente più che ottimo: la maggior parte delle mancanze risiedono più in metodi non ancora implementati per i tipi base che altro, ma John Lam ha chiarito come lui e il suo team abbiano preferito concentrarsi maggiormente su AST e parser ma soprattutto sulla struttura del core language, dispatcher per gestire da subito i blocchi tipici di Ruby e interoperabilità con .NET. Credetemi però, avere già tutto questo funzionante dopo pochi mesi e con buone prestazioni nonostante la scarsa ottimizzazione messa in atto è un risultato non indifferente, soprattutto nel contesto di un linguaggio che purtroppo manca ancora oggi di specifiche scolpite sulla pietra e dove l'unico set di specifiche è rappresentato dall'MRI ufficiale, di cui però il team di IronRuby non può consultare i sorgenti per motivi prettamente legali. Per un'altra iniezione di ottimismo basta poi dare un'occhiata al sorgente stesso di IronRuby potendone già constatare pulizia e chiarezza, tanto che nel giro di pochi minuti è possibile avere un'idea di massima su come e dove andare a mettere le mani per cominciare ad estendere in prima persona l'implementazione. Per quanto mi riguarda ho l'impressione che IronRuby si evolverà in un progetto molto importante e mi sembra che ci sia anche un crescente entusiasmo da parte dei rubysti che al tempo stesso sono o sono stati sviluppatori .NET, cosa che mi fa ben sperare per una buona partecipazione al progetto da parte della comunità.

Ora però cominciamo a fare qualche esperimento, compilando prima di tutto i sorgenti di IronRuby. Fortunatamente non è necessario avere installato Visual Studio o altri ambienti di sviluppo, basta avere una normale installazione di .NET 2.0 (anche soltanto redist, non serve l'SDK) e un editor di testo/codice decente. All'interno dell'archivio compresso troverete il file Build.cmd che contiene già le impostazioni per msbuild.exe, molto probabilmente però dovrete specificarne all'interno il path completo sostituendo le variabili. Nel mio caso, Build.cmd è diventato:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\msbuild.exe 
      /p:Configuration=Release /t:Rebuild IronRuby.sln

Per la cronaca IronRuby può essere compilato anche con Mono in Windows o Linux, anche se allo stato attuale è necessario sfruttare una build realizzata dai sorgenti nel repository SVN. La compilazione genererà tra le varie DLL anche l'eseguibile rbx.exe che è una sorta di incrocio tra ruby.exe (interprete) e IRB (console interattiva): eseguendolo senza alcun parametro RBX parte in modalità interattiva, in pieno stile IRB, altrimenti specificando un file/script in Ruby come parametro quest'ultimo verrà eseguito direttamente proprio come con l'interprete tradizionale. Per le nostre prime prove giocheremo un po' con la classe String. Anzi, per ora sarebbe meglio dire MutableString visto il seguente output da RBX:

>>> "CIAO".class
=> MutableString

L'output in IRB invece è:

irb(main):001:0> "CIAO".class
=> String

Come già esplicitamente detto da John Lam, questo comportamento verrà sistemato con i prossimi rilasci, tuttavia l'occasione ci permette di mettere in evidenza come una stringa in Ruby sia effettivamente mutabile dal momento che il suo contenuto può essere modificato senza necessariamente generare una nuova istanza. Proseguendo con le prove, si può incontrare fin da subito una testimonianza dell'incompletezza dei metodi per i tipi base:

>>> "CIAO".reverse
System.MissingMethodException: undefined local variable or method `reverse' for 
ciao:MutableString   in Ruby.Builtins.Kernel.MethodMissing(CodeContext context, Object self, 
Proc proc, SymbolId name, Object[] args) in j:\ir\pre-alpha1\Src\Ruby\Builtins\Kernel.cs:riga 169
[...]

La mancanza del metodo MutableString#reverse genera un'eccezione, tuttavia in Ruby potremmo intercettare la chiamata a un metodo non esistente sfruttando l'hook Object#method_missing che è chiamato automaticamente ogni volta che viene invocato un metodo non definito per un oggetto. Proviamo a sfruttare questo hook per vedere come si comporta IronRuby:

class MutableString def method_missing(method_name, *args) puts "Sorry, #{method_name} does not belong to me." end end

Basta copiare e incollare il codice in RBX (proprio come faremmo in IRB) e poi chiamare un metodo non definito per un oggetto di tipo MutableString, come per esempio lo stesso reverse:

>>> "CIAO".reverse
Sorry, reverse does not belong to me.
=> nil

Grande, Object#method_missing c'è e funziona! Però a noi il metodo MutableString#reverse serve proprio, riusciremo a sfruttare già in questa versione di IronRuby il concetto di classi aperte implementandolo direttamente via codice Ruby? Proviamo ampliando il codice di esempio di poco fa:

class MutableString def method_missing(method_name, *args) puts "Sorry, #{method_name} does not belong to me." end def reverse reversed = " " * self.length precalculated = self.length - 1 self.length.times do |i| reversed[i] = self[precalculated - i] end reversed end end

Come prima, incolliamo il codice in RBX e poi proviamo a invocare il metodo che abbiamo appena definito:

>>> "CIAO".reverse
=> "OAIC"
>>> "CIAO".my_undefined_method
Sorry, my_undefined_method does not belong to me.
=> nil

Funziona tutto, veramente ottimo! Ora però la faccenda comincia a farsi un po' più complicata perché vogliamo che il nostro metodo MutableString#reverse sia implementato "nativamente" in IronRuby, per motivi puramente prestazionali ma anche perché è giusto che sia così trattandosi di un metodo effettivamente appartenente alla classe String in Ruby. In pratica è giunto il momento di cominciare a giocare con i sorgenti in C# di IronRuby, anche se lo faremo in un messaggio successivo 🙂

... e IronRuby fu!

John Lam sul suo blog ha finalmente annunciato il primo rilascio del codice sorgente di IronRuby (versione pre-alpha1). Il suo post non è un mero annuncio ma fornisce qualche dettaglio interessante (oltre alla conferma del suo talk riguardante IronRuby ad OSCON), di cui però vorrei estrapolare questa parte mentre vi inviterei a leggere il resto:

We're also happy to announce that we will be accepting source code contributions into the IronRuby libraries. Right now we have a lot of logistical work that we still need to do, but we fully intend on getting IronRuby onto Rubyforge by the end of August.

Per conto mio si tratta di un'ottima notizia che tra l'altro mi sorprende anche non poco. BTW per chi non lo sapesse, anche se dal nome è facile intuirlo, Rubyforge è il sito che ospita la maggior parte dei progetti Ruby. Insomma che dire, a parte i complimenti a John e a tutti i membri del team di sviluppo? Ora inizia il bello, per tutti 🙂 Domani spero di ritagliarmi del tempo per cominciare a giocarci...