Quattro interpreti ruby a confronto

Ho scritto un articolo sul mio blog dove esamino il risultato di quattro
interpreti: 1.8.6, 1.9.1, JRuby e la nuova versione di Ironruby.
http://mastrodonato.info/index.php/2009/08/quattro-interpreti-ruby-a-confronto/

E’ interessante anche il confronto fatto da Antonio C.:

http://antoniocangiano.com/2009/08/04/a-faster-ruby-on-windows-is-possible/

Il giorno 07 agosto 2009 00.07, Marco
Mastrodonato[email protected] ha scritto:

Ho scritto un articolo sul mio blog dove esamino il risultato di quattro
interpreti: 1.8.6, 1.9.1, JRuby e la nuova versione di Ironruby.
http://mastrodonato.info/index.php/2009/08/quattro-interpreti-ruby-a-confronto/

Posted via http://www.ruby-forum.com/.


Ml mailing list
[email protected]
http://lists.ruby-it.org/mailman/listinfo/ml


Carlo P.
email: [email protected]
twitter: @carlopecchia

Le morali sono due:

  1. Usare << per concatenare le stringhe e stare alla larga da +, e anche
    #{} è lentino

  2. I single click installer per Windows compilati con mingw32 sono molto
    più veloci di quelli che i più usano, magari perché non sanno che ce ne
    sono di migliori

Grazie per i test!

Paolo

Carlo P. wrote:

E’ interessante anche il confronto fatto da Antonio C.:

http://antoniocangiano.com/2009/08/04/a-faster-ruby-on-windows-is-possible/

Il giorno 07 agosto 2009 00.07, Marco
Mastrodonato[email protected] ha scritto:

Ho scritto un articolo sul mio blog dove esamino il risultato di quattro
interpreti: 1.8.6, 1.9.1, JRuby e la nuova versione di Ironruby.
http://mastrodonato.info/index.php/2009/08/quattro-interpreti-ruby-a-confronto/

Posted via http://www.ruby-forum.com/.


Ml mailing list
[email protected]
http://lists.ruby-it.org/mailman/listinfo/ml


Carlo P.
email: [email protected]
twitter: @carlopecchia

Sì il risultato più interessante è sicuramente il << per concatenare (
a<<b<<c<<d etc…) non avrei mai detto che fosse più veloce di
%(#{a}#{b}#{c}#{d}#{e})

Grazie a te per i commenti e per il riassunto, purtroppo non sono
riuscito ad ottenere qualcosa di conciso e bello come quello fatto da
Antonio magari nel prossimo …e vedrò anche di fare qualche
comparazione con python e php.
Sulla differenza tra il nuovo 1.8.6 p368 e le prime versioni (il famoso
autoinstallante mi sembra sia la p111), effettivamente è abbastanza
marcata ma non so se per un miglioramento del codice dell’interprete o
per una migliore compilazione come dici tu.

Il giorno 08 agosto 2009 10.26, Paolo
Montrasio[email protected] ha scritto:

<< appende b ad a modificandone il valore.
c = “#{a}#{b}” non l’avrebbe fatto.
Mi viene da dire che spesso è quest’ultimo il comportamento che si vuole
avere e non il primo.

non me n’ero mai accorto! (anche perché usavo sempre #{}, solo che ora
ho visto i benchmark…
beh, questo significa che uno deve sempre fare partedamodificare <<
partedalasciareuguale, eventualmente con un ciclo…

a proposito, se chi ha fatto i benchmark volesse aggiungere alla
valutazione [].join mi farebbe cosa molto gradita: spesso quando devo
concatenare tante parti creo un array e faccio join alla fine. faccio
bene o faccio male?

pietro

Alessandro S. wrote:

Sì il risultato più interessante è sicuramente il << per concatenare (
a<<b<<c<<d etc…) non avrei mai detto che fosse più veloce di
%(#{a}#{b}#{c}#{d}#{e})

Attenzione però a usare << solo a proposito. Non l’avevo mai usato per
la concatenazione di stringhe e mi sono appena reso conto della sua
reale semantica

$ irb

a = “a”
=> “a”

b = “b”
=> “b”

c = a << b
=> “ab”

p a
“ab”

<< appende b ad a modificandone il valore.
c = “#{a}#{b}” non l’avrebbe fatto.
Mi viene da dire che spesso è quest’ultimo il comportamento che si vuole
avere e non il primo.

Paolo

Il giorno 08 agosto 2009 11.04, Simone C.[email protected] ha
scritto:

Riguardo alla domande sulle stringhe, è corretto. Ricordo uno splendido
articolo, credo scritto da Dave T., dove dimostrava come sia molto più
performante lavorare su un array di stringhe e poi eseguire il Join alla
fine rispetto a concatenare sulla stessa stringa. Purtroppo non riesco più a
trovare quell’articolo, dove veniva dimostrato come uno script che eseguiva
un parsing di un file (credo Csv) andava in crash per mancanza di risorse
nel primo caso mentre funzionava che una meraviglia con l’uso di Array e
Join.

ah, bene.

In linea di massima, le differenze sono irrilevanti per i semplici script
(pensiamo agli Helper di Rails) ma iniziano a diventare consistenti quando
si tratta di lavorare pesantemente su file di testo o parser.

vero, in generale vale il principio “performance is not a problem
until performance is a problem.”; il guaio con i linguaggi
interpretati, però, è che ci vuole un attimo a far diventare
esponenziale senza motivo un metodo, nel senso che, mentre in C, ad
esempio, ci si rende perfettamente conto di star ciclando su una
stringa tre volte ed è scontato chiedersi se non si possa ciclare solo
due volte o magari una, in ruby capita di ciclare 100’000 volte senza
accorgersene…

pietro

Non ho grandissima esperienza in termini di analisi dell’interprete Ruby
a
basso livello ma vi riporto alcune curiosità che ho sperimentato nel
tempo,
anche in seguito alla lettura di diversi articoli sul tema scritti da
why o
altri personaggi che senza dubbio sanno il fatto loro.

Riguardo a << prestate molta attenzione, come avrete notato (ieri non ho
avuto tempo di rispondere) modifica l’oggetto inplace. E questo è anche
il
motivo per cui è più performante dell’interpolazione.
In linea generale, in Ruby (e non solo), i metodi che lavorano
sull’oggetto
sono più performanti (parliamo comunque di valori praticamente
impercettivili) di quelli che lavorano ritornando una copia.

Esempio
gsub! è più performante di gsub
slice! è più performance di slice
e via dicendo.

Riguardo alla domande sulle stringhe, è corretto. Ricordo uno splendido
articolo, credo scritto da Dave T., dove dimostrava come sia molto
più
performante lavorare su un array di stringhe e poi eseguire il Join alla
fine rispetto a concatenare sulla stessa stringa. Purtroppo non riesco
più
a
trovare quell’articolo, dove veniva dimostrato come uno script che
eseguiva
un parsing di un file (credo Csv) andava in crash per mancanza di
risorse
nel primo caso mentre funzionava che una meraviglia con l’uso di Array e
Join.

In linea di massima, le differenze sono irrilevanti per i semplici
script
(pensiamo agli Helper di Rails) ma iniziano a diventare consistenti
quando
si tratta di lavorare pesantemente su file di testo o parser.


Simone C.

Site & Blog: http://www.simonecarletti.com
Email: [email protected]
LinkedIn: http://linkedin.com/in/weppos
Nick: weppos | Skype: weppos

Pietro G. wrote:

a proposito, se chi ha fatto i benchmark volesse aggiungere alla
valutazione [].join mi farebbe cosa molto gradita: spesso quando devo
concatenare tante parti creo un array e faccio join alla fine. faccio
bene o faccio male?

La risposta breve è: dipende da quanto sono grosse le stringhe, se sono
piccole [].join perde, se sono grandi (diciamo dal kB abbondante in su)
vince.

Segue la risposta lunga.

Leggendo il benchmark mi ha incuriosito proprio la variazione di
performance dei metodi di concatenazione di stringhe, che è
un’operazione che si fa di continuo. Mi sono chiesto allora quale fosse
il metodo più veloce tra tutti.

Ho allora ripetuto il benchmark delle stringhe aggiungendo altri due
metodi di concatenzione, ossia

%(#{stringa1} #{stringa2})

e

a = []; a << stringa1; a << stringa2; a.join(" ")

ed inoltre ho fatto la prova anche con stringhe lunghe, da 500, 1000,
2000 e 4000 caratteri l’una.

Innanzitutto ho notato che ripetendo un po’ di volte il test << #{} e
%() risultano praticamente identici. Qualche volta vince uno e qualche
volta vince un altro.

Per stringhe di un carattere la join dell’array è un po’ più lenta
mentre per stringhe di 500 caratteri inizia ad avvicinarsi agli altri.
Per stringhe lunghe + diventa estremamente più lento di tutti gli altri
metodi.

Questo è il risultato di un’instanza del test, sperando che la
formattazione sopravviva al post.

                       user     system      total        real

Concat 1.000.000:

  •                  2.370000   0.130000   2.500000 (  2.532409)
    

<< 2.310000 0.150000 2.460000 ( 2.493730)
#{} 2.410000 0.180000 2.590000 ( 2.582961)
%() 2.420000 0.150000 2.570000 ( 2.588971)
Array 4.250000 0.170000 4.420000 ( 4.445646)
big + 14.360000 0.240000 14.600000 ( 14.635854)
big #{} 6.210000 0.170000 6.380000 ( 6.401311)
big %() 6.330000 0.180000 6.510000 ( 6.522818)
big Array 6.500000 0.440000 6.940000 ( 6.956503)

Portando le stringhe da concatenare da 500 a 1000 caratteri la
concatenazione con Array rimane al livello degli altri. A 2000 inizia ad
essere leggermente più veloce e a 4000 è decisamente più rapida. Questo
è il caso da 4000:

big + 86.840000 0.270000 87.110000 ( 87.336664)
big #{} 30.950000 0.190000 31.140000 ( 31.217629)
big %() 31.100000 0.270000 31.370000 ( 31.472801)
big Array 26.140000 6.470000 32.610000 ( 32.672105)

I test sono stati eseguiti su un Intel Core Duo 2 T7200 (dual core 2.0
GHz), ruby 1.8.7 (2008-08-11 patchlevel 72) [x86_64-linux] 3.4 GB RAM.

Il codice Ruby da aggiungere al benchmark è

t1 = b.report(“big +”) do
sa="%04000d" % 1; sb="%04000d" % 2; sc="%04000d" % 3;
sd="%04000d" % 4; se="%04000d" % 5
n.times { sa + " " + sb + " " + sc + " " + sd + " " + se; sb + " " +
sc }
end
t1 += b.report(“big #{}”) do
sa="%04000d" % 1; sb="%04000d" % 2; sc="%04000d" % 3;
sd="%04000d" % 4; se="%04000d" % 5
n.times { “#{sa} #{sb} #{sc} #{sd} #{se}”; “#{sb} #{sc}” }
end
t1 += b.report(“big %()”) do
sa="%04000d" % 1; sb="%04000d" % 2; sc="%04000d" % 3;
sd="%04000d" % 4; se="%04000d" % 5
n.times { %(#{sa} #{sb} #{sc} #{sd} #{se}); %(#{sb} #{sc}) }
end
t1 += b.report(“big Array”) do
sa="%04000d" % 1; sb="%04000d" % 2; sc="%04000d" % 3;
sd="%04000d" % 4; se="%04000d" % 5
n.times { a = []; a << sa; a << sb; a << sc; a << sd; a << se;
a.join(" “);
a = []; a << sb; a << sc; a.join(” ") }
end

Il test big << manca perché come si è già visto nei post precedenti
l’operatore << modifica il suo argomento di sinistra e non avevo voglia
di includere dei literal da 4000 caratteri nel programma.

Conclusione: nella maggior parte dei casi [].join è più lento (e pure
meno leggibile) ma quando i dati sono tanti vale la pena usarlo.

Non ho fatto test per vedere se i vari metodi hanno diverse occupazioni
di memoria. Ci sono volontari?

Paolo

gsub! è più performante di gsub

gsub ha anche il brutto vizio di trasformare tutto in espressione
regolare, anche quando viene passata una semplice stringa.

La mia esperienza in Tcl indica che i glob ( [string match] ) sono
molto piu performanti dei regexp, in quanto piu semplici. Peccato
che non sia possibile optare per qualche algoritmo piu` semplice…


David N. Welton

http://www.welton.it/davidw/

http://www.dedasys.com/
Sent from Padua, Veneto, Italy

Paolo M. wrote:

Il test big << manca perché come si è già visto nei post precedenti
l’operatore << modifica il suo argomento di sinistra e non avevo voglia
di includere dei literal da 4000 caratteri nel programma.

Conclusione: nella maggior parte dei casi [].join è più lento (e pure
meno leggibile) ma quando i dati sono tanti vale la pena usarlo.

Non ho fatto test per vedere se i vari metodi hanno diverse occupazioni
di memoria. Ci sono volontari?

Paolo

Paolo, secondo me il confronto tra le concatenazioni e il [].join non è
equo perchè in questo utilizzi anche degli <<. L’ho modificato
leggermente, testa anche la generazione delle stringhe (una curiosità in
piu) ed ho aggiunto anche << anche se utilizzato in questo modo ha poco
senso ed è poco leggibile.
L’ho fatto girare sul mio portatile Intel core2 Duo T7200 2Ghz (166x12)
Ram 2Gb 667Mhz WinXP SP3:

require “benchmark”
include Benchmark

Benchmark.bm(21, “— Total:”) do|b|
puts “1.000.000 iterations:”
puts “‘1000’.create:”
n=1_000_000
t1 = b.report("’%01000d’ % 0") do
n.times { “%01000d” % 0 }
end
t1 += b.report(“0 * 1000”) do
n.times { “0” * 1000 }
end

puts “‘4000’.create:”
n=1_000_000
t1 += b.report("’%04000d’ % 0") do
n.times { “%04000d” % 0 }
end
t1 += b.report(“0 * 4000”) do
n.times { “0” * 4000 }
end

a=[]
(1…5).each {|x| a << “%01000d” % x}

puts “1000: str vs [].join:”
n=1_000_000
t2 = b.report("+") do
n.times { a[0] + " " + a[1] + " " + a[2] + " " + a[3] + " " + a[4];
a[1] + " " + a[2] }
end
t2 += b.report("#{}") do
n.times { “#{a[0]} #{a[1]} #{a[2]} #{a[3]} #{a[4]}”; “#{a[1]}
#{a[2]}” }
end
t2 = b.report("<<") do
n.times {
x = y = “”
x<<a[0];x<<" “;x<<a[1];x<<” “;x<<a[2];x<<” “;x<<a[3];x<<”
“;x<<a[4]
y<<a[1];y<<” “;y<<a[2]
}
end
t2 += b.report(”[].join") do
n.times { a.join(" ") }
end

[t1+t2]
end

C:\Lavoro\Progetti\Test\Bench>ruby -v
ruby 1.8.6 (2009-03-31 patchlevel 368) [i386-mingw32]

C:\Lavoro\Progetti\Test\Bench>ruby Join.rb
user system total real
1.000.000 iterations:
‘1000’.create:
‘%01000d’ % 0 6.187000 1.281000 7.468000 ( 7.593750)
0 * 1000 12.516000 0.390000 12.906000 ( 13.031250)
‘4000’.create:
‘%04000d’ % 0 15.641000 1.875000 17.516000 ( 17.656250)
0 * 4000 45.906000 2.125000 48.031000 ( 48.328125)
1000: str vs [].join:

  •                 36.234000  18.985000  55.219000 ( 55.921875)
    

#{} 15.125000 4.437000 19.562000 ( 19.812500)
<< 14.281000 3.703000 17.984000 ( 18.296875)
[].join 5.641000 3.875000 9.516000 ( 9.687500)
— Total: 100.172000 13.249000 113.421000 (114.593750)

In questo caso il .join è molto più veloce degli altri.
Questa è l’esecuzione di ruby 1.9.1:

C:\Lavoro\Progetti\Test\Bench>ruby -v
ruby 1.9.1p129 (2009-05-12 revision 23412) [i386-mingw32]

C:\Lavoro\Progetti\Test\Bench>ruby Join.rb
user system total real
1.000.000 iterations:
‘1000’.create:
‘%01000d’ % 0 10.984000 1.266000 12.250000 ( 12.312500)
0 * 1000 1.953000 0.547000 2.500000 ( 2.515625)
‘4000’.create:
‘%04000d’ % 0 20.125000 2.281000 22.406000 ( 22.578125)
0 * 4000 4.344000 2.938000 7.282000 ( 7.343750)
1000: str vs [].join:

  •                 36.359000  16.718000  53.077000 ( 53.500000)
    

#{} 13.141000 5.516000 18.657000 ( 18.859375)
<< 15.047000 5.375000 20.422000 ( 20.578125)
[].join 5.891000 4.047000 9.938000 ( 10.031250)
— Total: 58.344000 16.454000 74.798000 ( 75.359375)

I risultati di elaborazione sono molto simili, il risultato è stravolto
dai tempi di generazione delle stringhe, su ruby 1.8.6 sono molto alti
se generate con *, in compenso con % va meglio.
L’utilizzo di memoria (quasi identico tra le due versioni) vede in
leggero svantaggio .join (picco max di 12Mb) contro 8,5Mb degli altri
metodi… valutare se è una differenza trascurabile dipende dalla
complessità del progetto, l’importante è essere a conoscenza delle
differenze.

Li ho eseguiti anche con jruby e ironruby con risultati molto diversi,
ironruby è una scheggia quando usa %, conferma le sue migliori
prestazioni con i numeri rispetto alle stringhe. I dettagli li metterò
nel mio prossimo articolo :wink: (che terminerò quando avrò poco sonno)

Paolo M. wrote:

Da quel che
ho googlato non c’è accordo se la 1.8.6 sia più o meno veloce della
1.8.7. Quel che so è che apparentemente su Linux conviene compilarsi
l’interprete, cosa che faccio in produzione ma non sul mio pc di
sviluppo dove ho fatto girare il benchmark. Secondo
http://antoniocangiano.com/2008/12/10/reflections-on-the-ruby-shootout/
si ottiene un raddoppio di velocità .

Paolo

Ho appena compilato tutto in locale (passando anche a rails 2.3.3) vi
dirò se va + veloce (ma dalla prima impressione direi di sì)

ps.
se usate postgres-pr finchè non esce la gem aggiornata, per passare alla
2.3.3 dovete aggiungere a config/initializers/new_rails_defaults.rb
questo:

def PGconn.quote_ident(name)
%(“#{name}”)
end

altrimenti avete l’errore :
undefinedmethod `quote_ident’ forPGconn:Class

Ciao Marco,

nel codice misurato dal mio benchmark avevo deciso di includere anche
Array.<< perché ritenevo che l’inizializzazione dell’array fosse parte
integrante dell’operazione di concatenazione.

Sia con l’array che con (ad esempio) String.+ si parte con delle
stringhe e poi c’è l’operazione da misurare che per String.+ è
semplicemente farne la somma mentre nell’altro caso si deve mettere le
stringhe in un array e farne una join.
Dal mio punto di vista saltare l’inizializzazione dell’array favoriva
indebitamente Array.join rispetto agli altri metodi.

Fortunamente non è questione di punti di vista, altrimenti potremmo
discuterne per l’eternità , ma di come saranno fatte le nostre
applicazioni reali. Se le stringhe con cui lavoreremo sono già in un
array, dal tuo benchmark sappiamo che la join è sicuramente preferibile
a a[0]+a[1]+etc. Se non sono in un array, dal mio benchmark sappiamo che
per stringhe piccole conviene usare la concatenazione e non crearsi
l’array per farne la join.

In sostanza avevamo pensato a due scenari differenti e quindi abbiamo
ragione tutti e due :slight_smile:

Riporto infine i risultati del tuo benchmark sul mio pc che come cpu è
identico al tuo. Ho più RAM (ma durante il benchmark ho sempre avuto
liberi almeno 1.8 GB), un OS differente (Ubuntu 8.10 64 bit) e ruby
1.8.7.

                       user     system      total        real

1.000.000 iterations:
‘1000’.create:
‘%01000d’ % 0 3.560000 0.190000 3.750000 ( 3.753813)
0 * 1000 9.760000 0.110000 9.870000 ( 10.004395)
‘4000’.create:
‘%04000d’ % 0 7.040000 0.190000 7.230000 ( 7.247626)
0 * 4000 37.950000 4.840000 42.790000 ( 42.877476)
1000: str vs [].join:

  •                 23.570000   0.560000  24.130000 ( 24.184767)
    

#{} 10.160000 0.190000 10.350000 ( 10.368516)
<< 13.550000 0.180000 13.730000 ( 13.752579)
[].join 4.620000 0.750000 5.370000 ( 5.412506)
— Total: 76.480000 6.260000 82.740000 ( 83.048395)

Da me gira in 76 s contro 100 s da te. Mi chiedo se la differenza sia
dovuta alla versione di ruby o a come sono stati compilati gli
interpreti per Windows e per Linux. Non credo che l’OS c’entri in
qualche modo dato che stiamo misurando operazioni CPU bound. Da quel che
ho googlato non c’è accordo se la 1.8.6 sia più o meno veloce della
1.8.7. Quel che so è che apparentemente su Linux conviene compilarsi
l’interprete, cosa che faccio in produzione ma non sul mio pc di
sviluppo dove ho fatto girare il benchmark. Secondo

si ottiene un raddoppio di velocità .

Paolo

Paolo M. wrote:

Ciao Marco,

Ciao Paolo,
si erano due scenari differenti, scegliere quelli che più rispecchiano
il mondo reale della programmazione non è semplice perchè, come abbiamo
detto, ogni progetto ha un suo prerequisito. In questo caso, io cercavo
una linea che fosse un compromesso tra semplicita e performance.

Riguardo ai tuoi tempi di esecuzione, sono ottimi e confermano
l’importanza di avere un interpete efficiente ed ottimizzato per il
proprio sistema. Guarda se (sempre sul mio portatile) utilizzo il
vecchio mswin32:

C:\Lavoro\Progetti\Test\Bench>ruby -v
ruby 1.8.6 (2007-09-24 patchlevel 111) [i386-mswin32]

C:\Lavoro\Progetti\Test\Bench>ruby Join.rb
user system total real
1.000.000 iterations:
‘1000’.create:
‘%01000d’ % 0 7.735000 1.031000 8.766000 ( 8.766000)
0 * 1000 27.953000 0.454000 28.407000 ( 28.406000)
‘4000’.create:
‘%04000d’ % 0 20.500000 2.000000 22.500000 ( 22.594000)
0 * 4000 106.922000 2.593000 109.515000 (109.781000)
1000: str vs [].join:

  •                 42.796000  17.344000  60.140000 ( 60.422000)
    

#{} 17.500000 4.359000 21.859000 ( 21.953000)
<< 17.594000 3.782000 21.376000 ( 21.500000)
[].join 7.141000 3.468000 10.609000 ( 10.625000)
— Total: 187.845000 13.328000 201.173000 (201.672000)