Comincio facendo una premessa. Quando si deve sviluppare un progetto informatico non esiste il linguaggio del momento, o preferito, o “più bello”. Esiste un gruppo di lavoro con le sue competenze ed una problematica da risolvere. Le considerazioni che seguono sono basate su dei test che non mirano ad essere esaustivi. Lo scopo di quest’analisi è quello di porre domande e ottenere delle risposte. Per cui chiunque non concordasse con i risultati e fosse in grado di aggiungere nuovi tasselli al mosaico che si sta andando a costruire è ben accetto. Quindi… vado ad incominciare.
Era il 1991 mi venne chiesto di velocizzare un algoritmo che durava su un intel 386 la bellezza di 24 ore. Il software era scritto in quick basic che non brillava certo in performance. La soluzione fu di linkare la routine di calcolo scritta in C al programma quick basic. Magia!!! La procedura di calcolo durava solo più un ora.
Sempre in quegli anni era comune trovare sulle riviste test relativi alle performance dei linguaggi, così si trovavano i tempi di compilazione, quelli di esecuzione di sort, accesso ai dischi in maniera sequenziale e random. Tutte cose utilissime, perché i dischi erano da 20/40 Mb e i processori da 20 MHz e spesso privi di coprocessore matematico non c’era tutto il lusso che abbiamo adesso 🙂
Comunque la regola d’oro era che i programmi compilati sono più veloci di quelli interpretati… adesso però quasi tutti i nuovi linguaggi di programmazione hanno la caratteristica di essere o totalmente interpretati o in pseudo codice. La motivazione è che i processori moderni sopperiscono con la loro velocità al divario di performance tra un programma compilato ed uno interpretato. Ovviamente gli interpretati hanno alcuni vantaggi come la tipizzazione a runtime che possono essere utili nella creazione di framework, inoltre in ambienti WEB dove il collo di bottiglia è la banda passante alcuni tempi diventano trascurabili. Così giusto per curiosità ho deciso di fare un test, assolutamente non esaustivo, ma che mi ha lasciato a bocca aperta (leggete e scoprirete perché).
La mia idea era di vedere i tempi di accesso al disco e di sort in memoria di una “grossa” mole di dati. Mi sono creato un file di 1,29GB (mi sembrava molto grosso) pieno di stringhe ordinate in modo decrescente e ho scritto un programma di lettura e sort in maniera crescente misurando i tempi di esecuzione. Ovviamente sono partito da un bel programma objective-c dicendomi: “è compilato, ha le sue fondamenta nel C, il framework cocoa l’ha scritto Apple, sara una vera bomba” 🙂 vi anticipo che mi è esplosa tra le mani 🙂
Il codice è molto semplice:
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSLog(@"Inizio lettura");
NSString *stringa = [NSString stringWithContentsOfFile:@"NoteTrim.txt" encoding:NSISOLatin1StringEncoding error:nil];
NSArray *array = [stringa componentsSeparatedByString:@"\n"];
NSLog(@"Fine lettura");
NSLog(@"Inizio sort");
array = [array sortedArrayUsingSelector:@selector(compare:)];
NSLog(@"Fine sort");
[pool drain];
return 0;
}
Ed ecco i risultati:
– objective-c Lettura 32 s
– objective-c Sort 1,5 s

I tempi non mi dicevano un granché per cui ho accettato i 32 s per il caricamento e l’1,5 s per il sort fiducioso della legge: compilato più veloce dell’interpretato.
Per l’interpretato sono partito usando python, sicuro di un aiuto da parte di Juhan per colmare le mie lacune (l’aiuto c’è stato, oramai vale l’ugualianza Juhan = F1). Il risultato è il sorgente che segue:
#!/usr/bin/ env python
# -*- coding: utf-8 -*-
import time
try:
# lettura del file
t0 = time.time()
print "Tempo inizio: ", time.strftime("%H:%M:%S")
file_input = open("NoteTrim.txt", "ru")
lines = file_input.readlines()
file_input.close()
t1 = time.time()
print "Tempo fine: ", time.strftime("%H:%M:%S")
print "Tempo di caricamento dei dati: ", t1 - t0, "secondi"
# ordinamento del file
t0 = time.time()
print "Tempo inizio: ", time.strftime("%H:%M:%S")
lines.sort()
print "Elementi caricati: ", len(lines)
t1 = time.time()
print "Tempo fine: ", time.strftime("%H:%M:%S")
print "Tempo di ordinamento dei dati: ", t1 - t0, "secondi"
except(IOError):
print "Errore nell'elaborazione del file NoteTrim.txt"
quit()
Devo dire che ho trovato le istruzioni necessarie in un batter d’occhio e che su internet si trova veramente di tutto riferito a python e questo è sicuramente un vantaggio non indifferente.
Lancio il tutto e… mi cade la mascella per terra! 3,6 s per leggere il file e 0,38 s per il sort. Giuro che mi è caduto in testa un macigno 🙂 e meno male che ho la testa dura!!!

I più accorti mi faranno notare che la lettura di un file dipende molto dal sistema operativo e che quindi avrei risultati diversi a parità di computer ma con S.O. differenti e che il sort di stringhe dipende da come vengono trattate le stringhe sempre a livello di S.O. e di linguaggio, ma il divario è sconcertante!
Per cui decido di approfondire la questione (ci ho impiegato i mesi trascorsi dall’ultimo articolo), molto per curiosità e molto perché la differenza tra compilati e interpretati era un concetto dei tempi delle scuole.
Mi accorgo che la versione di python è la 2.7.1 per cui mi chiedo “chissà che performance ci saranno con la 3.0 così ingenuamente lancio lo stesso sorgente… e chiamo Juhan 🙂 il sorgente che prima funzionava era diventato pieno di errori.
Per uno strano motivo non interpretava correttamente i tab e così ho dovuto ribattere tutto il sorgente. Poi ho scoperto che è cambiata la sintassi della print 😦 adesso ha bisogno delle parentesi e per ultimo nella open dovevo specificare l’encoding dei caratteri altrimenti il tutto non parte. Per cui il nuovo sorgente é diventato:
#!/usr/local/bin/ env pythonw3
# -*- coding: iso-8859-1 -*-
import time
try:
# lettura del file
t0 = time.time()
print("Tempo inizio: ", time.strftime("%H:%M:%S"))
file_input = open("NoteTrim.txt", "r", encoding='cp1252')
lines = file_input.readlines()
file_input.close()
t1 = time.time()
print("Tempo fine: ", time.strftime("%H:%M:%S"))
print("Tempo di caricamento dei dati: ", t1 - t0, "secondi")
# ordinamento del file
t0 = time.time()
print("Tempo inizio: ", time.strftime("%H:%M:%S"))
lines.sort()
print("Elementi caricati: ", len(lines))
t1 = time.time()
print("Tempo fine: ", time.strftime("%H:%M:%S"))
print("Tempo di ordinamento dei dati: ", t1 - t0, "secondi")
except(IOError):
print("Errore nell'elaborazione del file NoteTrim.txt")
quit()
e i tempi diventano 11,78 s per il caricamento del file e 1,65 per il sort. Le performance erano calate esponenzialmente quasi 4 volte più lento in lettura e nel sort.

Spinto dalla curiosità provo il tutto sotto winzot (non è un errore ma l’incrocio tra windows e lo ZOT dei fumetti di BC) in una macchina virtuale… Primo e unico problema con la 2.7.2 devo modificare la open. Non accetta l’opzione “ru” e devo sostituirla con la sola “r” e i tempi diventano:
– Python 2.7 Lettura 36,66 s
– Python 2.7 Sort 0,88 s

Con la versione 3 non devo fare modifiche e ottengo i seguenti tempi:
– Python 3.2 Lettura 60,70 s
– Python 3.2 Sort 2,58 s

Qui comincio a pormi alcune domande:
– Come mai a parità di S.O. la nuova versione di python è più lenta
– Perché non è stata mantenuta la piena compatibilità tra la 2.7 e la 3.2 visto che in un sorgente di 20 righe ne ho dovute modificare ben 9
– Perché ho delle diversità a livello di S.O. nella scrittura del codice. I parametri della open non vengono accettati a parità di versione.
Sono quei tarli che ti assillano che potrebbero essere lasciati tranquillamente in disparte. Sicuramente i programmi python non sono pieni di print come il mio esempio e sicuramente ci sono state innovazioni in altre parti del linguaggio che non sono ricadute nel mio test… e io non sono un esperto python, ne voglio vantarmi di esserelo. Però bisogna porsi delle domande e non si può lasciare correre tutto, poi arrivando da anni di Turbo Pascal prima e Delphi dopo è la prima volta che ricado in un porting problematico (dove cioè devo modificare un sorgente che non funziona più)… ma questi magari saranno spunti per un nuovo post.
Preso dalla curiosità e invidioso di Juhan che scrive articoli più interessanti dei miei provo anche il newlisp. Quante righe di codice? 4 contando anche la shell.
#!/usr/bin/newlisp
(println "Lettura: " (time (set ‘data (parse (read-file "NoteTrim.txt") "\n" 0))) " ms")
(println "Sort: " (time (sort data)) " ms")
(exit)
Risultati? Impressionanti anche questi. Lettura 4,66 s e sort in 1,7 secondi. Decido di provare la cosa anche sotto windows ma purtroppo durante il caricamento del file ottengo un errore di memoria… indagherò e vi farò sapere.

Quindi riassumiamo:
|
OSX Lion
|
Windows 7 64 Bit
|
|
Lettura (s)
|
Sort (s)
|
Lettura (s)
|
Sort (s)
|
Objective-C
|
32
|
1,5
|
–
|
–
|
Python 2.7
|
3,6
|
0,38
|
36,66
|
0,88
|
Python 3.2
|
11,78
|
1,65
|
60,70
|
2,58
|
NewLisp
|
4,66
|
1,7
|
–
|
–
|
A questo punto direi di saltare alle conclusioni di questa prima parte.
– I linguaggi interpretati sono veloci e certe verità del passato vanno riviste.
– I nuovi linguaggi sono compatti e pieni di librerie notevoli, per non dire incredibili (in newlisp solo 4 righe di codice con dei risultati di prima categoria).
A questo punto passiamo tutti a python o al lisp? Ovviamente no. Non tutti i software leggono e sortano file, abbiamo calcoli in virgola mobile, interfacce grafiche, applicazioni real time. Quindi i linguaggi compilati devono essere usati solo per applicazioni specifiche e sono meno performanti? Anche questo non è vero. L’applicativo objective-C si basava sulle librerie ad alto livello, ma la base di objective-C è il C che è tante cose, ma lento proprio no. Per cui è il momento di andare a mettere le mani sotto il cofano e vedere se è possibile migliorare… ma questo lo vedremo nella prossima puntata.