Il documento presenta il lavoro di tesi svolto da Michele Damian presso la facoltà di Ingegneria Informatica dell'università di Bologna. Lo scopo della dissertazione è quello di analizzare il comportamento di tuProlog (un interprete per il linguaggio di programmazione logica Prolog) e individuarne i punti critici al fine di migliorarne le prestazioni sia in termini di tempi di esecuzione che di gestione della memoria.
Daniele Lunassi, CEO & Head of Design @Eye Studios – “Creare prodotti e servi...
Analisi di prestazione dell'interprete tuProlog su piattaforma Java - Tesi
1. `
ALMA MATER STUDIORUM - UNIVERSITA DI BOLOGNA
`
FACOLTA DI INGEGNERIA
Corso di Laurea Triennale in Ingegneria Informatica
Tesi di Laurea in Fondamenti di Informatica L-B
Analisi di prestazione
dell’interprete tuProlog
su piattaforma Java
Candidato Relatore
Michele Damian Chiar.mo Prof. Enrico Denti
Anno Accademico 2007/08
5. Elenco delle figure
1.1 Il pattern state che realizza la macchina a stati finiti . . . . . . . . 3
1.2 La macchina a stati finiti del motore tuProlog . . . . . . . . . . . . 4
3.1 Analisi delle chiamate ai metodi pi` utilizzati nell’esecuzione delle
u
teorie. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.1 Analisi delle istanziazioni degli oggetti pi` allocati nell’esecuzione
u
delle teorie. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
iii
6.
7. Elenco delle tabelle
3.1 Tempi di esecuzione per cento risoluzioni e errore relativo calcolato
per una singola esecuzione. . . . . . . . . . . . . . . . . . . . . . . . 15
3.2 Tempi di lettura ed esecuzione. . . . . . . . . . . . . . . . . . . . . 16
3.3 Analisi dei metodi. . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.4 Analisi dei metodi totale. . . . . . . . . . . . . . . . . . . . . . . . . 20
3.5 Analisi della scalabilit` del motore tuProlog. . . . . . . . . . . . .
a . 21
3.6 Associazione fra il numero identificativo di un nodo nella Figura 3.1
e il metodo che esso rappresenta. . . . . . . . . . . . . . . . . . . . 23
4.1 Analisi degli oggetti utilizzati. . . . . . . . . . . . . . . . . . . . . . 30
4.2 Analisi dell’utilizzo della memoria da parte degli oggetti rispetto
all’obiettivo della teoria. . . . . . . . . . . . . . . . . . . . . . . . . 31
4.3 Analisi complessiva degli oggetti utilizzati. . . . . . . . . . . . . . . 32
4.4 Analisi del garbage collector. . . . . . . . . . . . . . . . . . . . . . . 35
4.5 Associazione fra il numero identificativo di un nodo nella Figura 4.1
e l’oggetto che esso rappresenta. . . . . . . . . . . . . . . . . . . . . 36
5.1 Comparazione dei tempi di esecuzione nelle singole teorie. . . . . . . 40
5.2 Comparazione dell’allocazione della memoria nelle singole teorie. . . 40
v
8.
9. Introduzione
Il motore tuProlog ` un interprete per il linguaggio Prolog, scritto interamente
e
in Java e interoperabile con le librerie fornite dalla piattaforma di sviluppo JDK.
Giunto recentemente alla versione 2.1, ha portato con s´ diversi cambiamenti dal
e
punto di vista strutturale. L’obiettivo del lavoro ` quello di analizzare le pre-
e
stazioni dell’interprete in modo tale da porre delle basi utili per un suo ulteriore
miglioramento in versioni successive. Alla conclusione, all’eventuale revisore del-
l’interprete, sar` chiaro su quali parti del codice dovr` orientare la sua attenzione
a a
per apportare dei miglioramenti. A questo proposito l’analisi viene scomposta in
due sezioni, con lo scopo di mettere in luce l’utilizzo della cpu e l’allocazione della
memoria heap da parte dei metodi e delle classi implementate dal motore. Per
quanto riguarda le prestazioni in termini di velocit`, allo stato attuale, i dati a di-
a
sposizione si basano unicamente sui tempi di esecuzione di alcune teorie utilizzate
come benchmark per interpreti Prolog e considerate standard per test prestazionali
di questo tipo di motore. A causa della variazione del sistema hardware sul quale
vengono eseguiti i test, queste teorie per` non garantiscono tempi di esecuzione ab-
o
bastanza elevati da poter essere misurati con un apprezzabile errore sulla misura.
Per questo motivo ad esse devono essere aggiunte delle nuove teorie per ottimizzare
il benchmark. Un altro obiettivo che il lavoro si pone ` quello di analizzare come
e
sono cambiate le prestazioni dell’interprete in seguito al passaggio dalla versione
1 alla versione 2, la quale ha introdotto alcune modifiche a livello architetturale.
In particolare viene presa come riferimento l’ultima release di ognuna delle due
versioni, le quali sono rispettivamente la release 1.3 e 2.1.
La dissertazione ` strutturata in modo da rendere pi` comprensibile possi-
e u
bile il motivo delle scelte fatte durante il progetto. Il Capitolo 1 presenta il motore
vii
10. tuProlog, spiegando quali sono le caratteristiche principali dell’interprete e facendo
un’analisi di alto livello del suo funzionamento. Nel Capitolo 2 vengono discussi
i motivi che hanno portato alla scelta di HPROF come strumento di profiling,
nonch´ le sue funzionalit` principali. Inoltre vengono presentate le teorie di test
e a
che hanno permesso di ricavare le informazioni utilizzate nell’analisi di tuProlog.
Nei capitoli successivi si entra nella parte centrale del lavoro, esponendo queste
informazioni e analizzando qual ` il loro significato. In particolare, nel Capitolo 3
e
vengono analizzati i tempi di esecuzione, mentre nel Capitolo 4 viene analizzato
l’utilizzo della memoria heap da parte di tuProlog. Nel Capitolo 5 si trova un
confronto fra la pi` recente versione di tuProlog (2.1) e la versione 1.3. Questo
u
confronto viene fatto riferendosi ai tempi di esecuzione ed ai dati relativi all’uso
della memoria. Infine, nel Capitolo 6, vengono tratte le conclusioni, esponendo
quali sono le parti del motore che possono essere migliorate.
viii
11. Capitolo 1
Il motore tuProlog
L’obiettivo per cui l’interprete tuProlog venne ideato fu quello di poter disporre
di uno strumento in grado di realizzare dei componenti intelligenti da poter utiliz-
zare in un’infrastruttura internet dinamica. La base sulla quale viene costruito ` il
e
linguaggio di programmazione logica dichiarativo Prolog. Sin dal primo interprete,
scritto nel 1972, le implementazioni dei sistemi Prolog si pongono come obiettivo
quello di ottimizzare fattori quali il tempo di esecuzione e l’utilizzo della memoria.
tuProlog, scritto interamente in Java, si discosta da questa tendenza, introducendo
nell’architettura dell’interprete i concetti legati alla programmazione orientata agli
oggetti, con lo scopo di poterne sfruttare i vantaggi. Tra questi pregi, apprezzati
quando si deve scrivere codice utilizzabile in infrastrutture intelligenti e sistemi
dinamici, vi sono: la portabilit`, ovvero la facilit` con cui ` possibile distribuire
a a e
l’applicazione, legata solamente alla presenza della piattaforma Java; la riusabi-
lit`, che permette il riutilizzo di componenti in diversi sistemi; la modularit`, che
a a
determina anche la leggerezza del core. Queste nozioni, ben note nell’ingegneria
del software, vengono realizzate implementando i pattern descritti in seguito.
In primo luogo viene considerata la riusabilit` e la modularit` dell’architet-
a a
tura del motore tuProlog. Esse vengono rese possibili grazie alla separazione delle
responsabilit` del sistema. Ogni responsabilit` viene associata ad un singolo ma-
a a
nager il quale controlla parte del motore tuProlog, in modo tale che le librerie e
i predicati primitivi siano indipendenti dal motore e possano essere sostituite o
1
12. CAPITOLO 1. IL MOTORE TUPROLOG
modificate preservando il funzionamento del sistema. Queste caratteristiche sono
rese possibili grazie al pattern Fa¸ade [2], che riduce anche il numero di oggetti
c
che il cliente deve utilizzare, rendendo il sistema pi` semplice da gestire. Sono
u
presenti quattro tipi di manager per gestire diversi oggetti: il manager delle li-
brerie, il manager delle primitive, il manager delle teorie e il manager degli stati
della macchina a stati finiti. Il manager delle librerie rende possibile il caricamento
delle librerie di predicati al bisogno, anche a runtime. Questa peculiarit`, oltre
a
a rendere estremamente configurabile tuProlog, fa si che il core metta a disposi-
zione funzionalit` essenziali e quindi dia migliori prestazioni in termini di tempi
a
di esecuzione e utilizzo della memoria. Tuttavia esistono alcuni predicati che non
sono stati inseriti nelle librerie, ma sono stati definiti nel motore. Essi sono quei
predicati che influenzano il processo di risoluzione, come cut/0, quelli che sono
troppo basilari per essere definiti altrove, come fail/0 e quelli che per ragioni di
efficienza devono essere definiti vicino al core, come ’,’/2. A parte queste eccezioni
tutti gli altri predicati sono contenuti all’interno di librerie, le cui seguenti sono
caricate durante la fase di creazione del motore: la libreria BasicLibrary, la quale
contiene i predicati pi` importanti, escludendo i predicati di I/O; la libreria IO-
u
Library che mette a disposizione alcuni dei predicati di input/output standard di
Prolog, come write/1, read/1 e nl/0; la libreria ISOLibrary che definisce predicati
standard ISO non definiti nelle due librerie precedenti; e JavaLibrary che definisce
tutti quei predicati idonei a permettere l’interazione fra Prolog e Java. Tutte le
altre librerie, come quelle definite dall’utente, possono essere caricate runtime. Il
manager delle primitive ha il compito di riconoscere i predicati presenti all’interno
delle teorie da quelli definiti nelle librerie o dall’utente. Questa operazione ` neces-
e
saria in quanto i due gruppi di predicati dovranno essere trattati differentemente
durante il processo di risoluzione: i primi dovranno essere eseguiti come metodi
Java ogni qualvolta la risoluzione di una teoria preveda prima la loro risoluzione,
mentre gli altri saranno gestiti dalla macchina a stati finiti del motore tuProlog.
Il parser del motore tuProlog legge la teoria Prolog riconoscendo la corretta sin-
tassi e creando gli oggetti corrispondenti ai termini identificati. A supporto del
parser il manager delle teorie crea il database delle clausole, a cui il motore far` a
riferimento durante il processo di risoluzione e riconosce le direttive che devono
essere eseguite immediatamente. Per questioni di ottimizzazione il corpo di una
2
13. Figura 1.1: Il pattern state che realizza la macchina a stati finiti
proposizione viene decomposto in sotto obiettivi non appena essa stessa viene rico-
nosciuta e non durante la fase di risoluzione. La proposizione viene rappresentata
in memoria tramite una struttura dati ad albero in cui ogni foglia rappresenta un
sotto obiettivo.
Infine il manager del motore si occupa di gestire i motori di risoluzione crean-
do un’istanza della macchina a stati finiti per ogni teoria che debba essere risolta
(situazione che potrebbe presentarsi nel caso le teorie siano innestate l’una nell’al-
tra). Questa macchina, nel quale ogni stato rappresenta un passo nella risoluzione
della teoria, ` di fondamentale importanza in quanto realizza il motore di riso-
e
luzione delle teorie. Inoltre, il manager del motore permette di disaccoppiare il
motore di risoluzione tuProlog dal resto dell’architettura favorendo l’estensibilit`a
della macchina a stati finiti, a cui diventa possibile aggiungere nuovi stati. Per
realizzare tutto ci` viene usato il pattern State [2] che permette di istanziare og-
o
getti il cui comportamento dipende dallo stato interno. La classe Engine mantiene
al suo interno sia lo stato attuale della macchina a stati finiti sia lo stato suc-
cessivo al quale deve transitare, mentre gli stati vengono realizzati derivando la
classe astratta State. Pi` dettagliatamente il motore comprende uno stato iniziale
u
(Init), quattro stati rappresentanti le attivit` svolte durante il processo di riso-
a
luzione (Goal Selection, Goal Evaluation, Rule Selection e Backtrack) e quattro
stati finali rappresentanti i modi in cui la risoluzione potrebbe terminare (HALT,
TRUE, TRUE CP e FALSE).
3
14. CAPITOLO 1. IL MOTORE TUPROLOG
Figura 1.2: La macchina a stati finiti del motore tuProlog
Lo stato Init ` il punto di partenza e ha il compito di inizializzare il motore
e
di risoluzione estraendo gli obiettivi da valutare dalla teoria e creando un ogget-
to che rappresenta il contesto nella quale l’obiettivo verr` valutato. Dopodich´
a e
il controllo verr` passato allo stato Goal Selection il quale seleziona l’obiettivo
a
da valutare da una lista di obiettivi. Se questo obiettivo non viene trovato e il
processo di esecuzione ` terminato si possono presentare due casi: se esiste un
e
punto di scelta e quindi la teoria pu` avere un’altra soluzione, lo stato finale sar`
o a
TRUE CP, altrimenti lo stato finale sar` TRUE. Se invece esiste un altro obiettivo
a
che pu` essere estratto deve essere valutato nello stato Goal Evaluation, a meno
o
che esso non rappresenti un numero, in qual caso la risoluzione termina nello stato
FALSE. Se l’obbiettivo valutato nello stato Goal Evaluation rappresenta un pre-
dicato primitivo allora la macchina a stati finiti transita nello stato Goal Selection
o Backtrack, a seconda se la valutazione del predicato abbia avuto esito positivo o
negativo, altrimenti transita nello stato Rule Selection e viene scelta una clausola
compatibile per la risoluzione della teoria. In qualsiasi momento venga raggiunta
una condizione di errore nello stato Goal Evaluation la macchina transita nello
stato HALT e termina l’esecuzione. Nel caso non venga trovata nessuna clausola
dal set di regole compatibili con l’attuale obiettivo allora lo stato successivo sar`
a
quello di Backtrack. Nello stato di Rule Selection viene preparato un nuovo con-
testo di esecuzione, l’obiettivo viene unificato con la testa della clausola, il corpo
della clausola viene aggiunto al risolvente, viene creato un nuovo punto di scelta ed
il sistema transita nello stato di Goal Selection. Infine lo stato di Backtrack ha il
4
15. compito di trovare una clausola compatibile dal contesto creato nell’ultimo punto
di scelta aperto, se questa non viene trovata il motore termina la sua esecuzione
nello stato FAIL.
5
16.
17. Capitolo 2
Benchmarks
La scelta del benchmark ` un passo importante nell’analisi delle prestazioni di
e
un software. Le funzionalit` del benchmark, il modo in cui le prestazioni vengono
a
da esso misurate e il modo in cui i dati finali vengono rappresentati determinano
quali parti del sistema ` possibile analizzare, come ` possibile analizzarle attraverso
e e
lo strumento e come i dati possono essere interpretati e rielaborati in modo che essi
abbiano un significato concreto, tale da mettere alla luce le lacune del software esa-
minato. Il capitolo ` diviso in due sezioni. La prima descrive brevemente i possibili
e
profiler adatti ad un’analisi di memoria e velocit` di esecuzione di un’applicazione
a
Java. Vengono poi confrontati i vari strumenti ed esposte le ragioni che hanno por-
tato alla scelta dell’uno anzich´ dell’altro, soffermandosi sulle caratteristiche del
e
profiler HPROF, che fornisce i dati analizzati nei capitoli successivi. La seconda
sezione riguarda invece le teorie di test, essenziali per esaminare il comportamento
del sistema attraverso il profiler. La risoluzione delle clausole di Horn attraverso
l’interprete tuProlog viene infatti eseguita per una teoria ben precisa, in quanto
ogni teoria determina una successione dei cambiamenti di stato della macchina
a stati finiti diverso dalle altre e di conseguenza un diverso comportamento del
motore tuProlog. Quindi, durante la scrittura delle teorie, si considera che esse
devono testare l’interprete in modo tale da poterne analizzare le prestazioni in
tutti i possibili impieghi.
7
18. CAPITOLO 2. BENCHMARKS
2.1 Profilers
Il profiler ` essenzialmente uno strumento in grado di misurare le performance
e
e analizzare il comportamento di un certo software durante la sua esecuzione. Il
modo in cui questo viene attuato ed il modo in cui i dati collezionati vengono
resi disponibili al cliente varia da profiler a profiler. La scelta dello strumento pi` u
adatto allo scopo ` dettata non solo da quali variabili si debbano misurare durante
e
il profiling, ma anche dalla qualit` e quantit` delle informazioni che ` possibile
a a e
ricavare analizzando i dati forniti. Ad esempio, la scelta di un profiler in grado
di misurare, o in grado di fornire dati dal quale sia possibile misurare, la memo-
ria allocata da metodi, anche innestati, viene privilegiata rispetto ad un profiler
che fornisce solamente i dati relativi alla memoria allocata durante l’esecuzione
dell’applicazione. La qualit` delle informazioni ricavate pu` anche essere definita
a o
come la varianza dei dati misurati dallo strumento in esecuzioni diverse della stes-
sa teoria o in esecuzioni della stessa teoria su piattaforme diverse. Maggiore ` lae
varianza, minore sar` la qualit` delle informazioni in possesso. Questo problema `
a a e
reso evidente dal fatto che i dati ricavati saranno disponibili per future estensioni o
modifiche, cosa difficilmente possibile nel caso queste informazioni non siano para-
gonabili con quelle misurate su altre piattaforme. Sebbene la risoluzione di questo
quesito meriti una discussione a s´ ` possibile ovviare al problema esprimendo i
ee
tempi di esecuzione dei vari metodi1 con un valore relativo al tempo di esecuzione
totale della teoria, anzich´ come valore assoluto. In questo modo si possono avere
e
informazioni relative a quanto incide un metodo sul tempo di esecuzione totale e
questo dato rimane coerente fra diverse esecuzioni dell’interprete in diverse piat-
taforme2 .
Dato per scontato che lo strumento con cui viene fatto il profiling debba essere
in grado di misurare la quantit` di memoria heap allocata e i tempi di esecuzione
a
Si vedr` pi` avanti che viene assunto come ipotesi il fatto che il tempo di esecuzione totale
au
1
della teoria sia dato dalla somma del tempo di esecuzione dei singoli metodi e l’ottimizzazione
di uno di essi comporta l’ottimizzazione del tempo di esecuzione totale.
Non ` escluso che alcuni sistemi operativi, o alcune implementazioni JVM diverse da quella
e
2
della Sun Microsystems, presentino ottimizzazioni che potrebbero far variare le velocit` di ese-
a
cuzione relative dei vari metodi. Queste variazioni dovrebbero comunque essere ragionevolmente
piccole.
8
19. 2.1. PROFILERS
di un’applicazione Java, essendo tuProlog scritto interamente in Java, la scelta del
profiler viene fatta in primo luogo fra due categorie: i profiler commerciali e quelli
non commerciali3 . Data la natura accademica della ricerca si opta per un profiler
con licenza di tipo non commerciale. Questa scelta ` dettata dal fatto che utiliz-
e
zare un software commerciale porrebbe delle barriere economiche ad una futura
prosecuzione dell’analisi svolta. Un altro fattore di interesse nella scelta dello stru-
mento pi` consono `, come detto in precedenza, la possibilit` di misurare i tempi
u e a
di esecuzione e la quantit` di memoria allocata dai vari metodi utilizzati durante
a
la risoluzione di una teoria. Inoltre, osservando l’architettura della macchina a
stati finiti, si pu` facilmente intuire che alcuni degli stati in cui il sistema transita
o
`
vengono utilizzati in maniera predominante rispetto ad altri. E quindi opportuno
che la scelta venga indirizzata su un profiler che fornisca anche il numero di chia-
mate ad un metodo, in modo da poter analizzare meglio se il sistema spende un
certo tempo, o una certa quantit` di memoria, perch´ effettua un numero elevato
a e
di chiamate a questo o perch´ esso ` computazionalmente complesso.
e e
Una buona parte dei profiler con le caratteristiche indicate ` basata sull’in-
e
terfaccia JVM TI (acronimo di Java Virtual Machine Tool Interface) fornita nella
piattaforma J2SE della Sun Microsystems a partire dalla versione 1.5. Questa
interfaccia provvede un accesso allo stato della JVM da parte di altri strumenti
attraverso delle API. All’atto dell’inizializzazione della VM una libreria, scritta in
C o C++, viene caricata. Questa pu` invocare chiamate o registrarsi ad eventi,
o
secondo quanto descritto dall’interfaccia, allo scopo di interrogare la VM riguardo
al suo stato. Nei seguenti capitoli i dati sono tutti ricavati dalla libreria HPROF.
Questo agente, a differenza degli altri strumenti a disposizione, presenta i risul-
tati in maniera piuttosto grezza, senza che vengano elaborati o formattati. Quel
che pu` sembrare un difetto, o una mancanza di qualit`, ` in realt` un pregio.
o ae a
Gli altri profiler infatti, presentando un output pi` o meno elaborato, tendono a
u
mettere in risalto un aspetto del sistema anzich´ un altro, perdendo informazione.
e
Con HPROF ` possibile invece elaborare i dati grezzi, in modo tale da mettere in
e
risalto l’aspetto desiderato.
Per software non commerciale si intende shareware o open source.
3
9
20. CAPITOLO 2. BENCHMARKS
2.1.1 Il profiler HPROF
HPROF genera le informazioni relative al profiling attraverso le chiamate a
JVM TI, le chiamate di ritorno dagli eventi della JVM TI e attraverso l’iniezione
di byte code nelle classi caricate dalla VM. Il metodo usato per il profiling dipende
dall’opzione con cui viene lanciato HPROF: l’opzione cpu=times inietta il byte
code all’ingresso e all’uscita di ogni metodo, l’opzione heap inietta il byte code nel
metodo <init> della classe java.lang.Object ed in ogni ‘newarray’ visto all’interno
di tutti i metodi, mentre l’opzione cpu=sample utilizza un thread che, svegliandosi
`
dopo un determinato intervallo di tempo, campiona lo stack. E possibile utilizzare
questo strumento attraverso il comando
java -agentlib:hprof[=opzioni] ClasseDaAnalizzare
dove la ClasseDaAnalizzare ` la classe alice.tuprolog.Agent con, come primo
e
parametro, la teoria di test. In particolare, per fare il profiling dei tempi di ese-
cuzione e dell’uso dell’heap dell’interprete tuProlog, vengono usati i comandi con
le seguenti opzioni: hprof=cpu=times e hprof=heap=sites. Il primo presenta i
dati riguardanti al tempo effettivo speso da un metodo in millisecondi, il numero
di volte che quel metodo ` stato chiamato da un altro metodo e il trace dello stack
e
che ha causato quella chiamata. Il secondo comando mostra gli oggetti che sono
stati creati durante l’esecuzione del programma. In particolare si possono trovare
informazioni riguardanti la memoria occupata e il numero di oggetti vivi in un
certo trace, nonch´ la memoria allocata e il numero di oggetti allocati durante l’e-
e
secuzione dell’intero programma nello stesso trace. Questo permette di analizzare
il comportamento del garbage collector durante l’esecuzione della teoria. Inoltre ad
ogni oggetto viene associato il trace dello stack che ha generato la sua allocazione
in memoria, permettendo di fatto di risalire a quale metodo lo ha istanziato. Uno
degli svantaggi di usufruire di un’analisi metodo a metodo, anche se compensato
dalla precisione delle misure, ` quello di non poter sfruttare questo procedimento
e
per teorie il cui calcolo computazionale richieda tempi lunghi, in quanto il pro-
filer allunga, anche notevolmente, i tempi di esecuzione. Di questo e dei motivi
che hanno portato alla scelta delle opzioni descritte in precedenza viene discusso
comunque pi` approfonditamente nei Capitoli 3 e 4.
u
10
21. 2.2. TEORIE DI TEST
2.2 Teorie di test
Le teorie di test sono una parte del benchmark importante quanto il profiler.
Esse infatti determinano il comportamento dell’interprete durante la risoluzione
delle clausole e perci` devono essere scelte con cura, in modo tale da permettere
o
un’analisi il pi` completa possibile. In questa dissertazione vengono presentati
u
i risultati dei test sul core tuProlog e sulla libreria BasicLibrary, senza testare
le librerie aggiuntive come IOLibrary, JavaLibrary o ISOLibrary, anche se queste
vengono caricate automaticamente durante l’inizializzazione del motore tuProlog.
L’attenzione ` anche rivolta alla scalabilit` del sistema e alcune delle teorie uti-
e a
lizzate sono scritte in modo tale da permettere vari test sulla scalabilit` senza la
a
necessit` di pesanti modifiche alla teoria stessa o l’introduzione di nuove teorie.
a
I risultati trovati in studi precedenti [6], con lo scopo di confrontare tuPro-
log con altri interpreti Prolog basati su Java, vennero trovati utilizzando teorie di
test note, utilizzate in benchmark atti a misurare la velocit` di risoluzione delle
a
teorie. Per mantenere una certa continuit` e per poter confrontare i risultati del
a
lavoro svolto in passato si ` deciso di mantenere, per quanto possibile, alcune delle
e
teorie gi` utilizzate. Si noti comunque che i sistemi hardware e software su cui
a
sono stati fatti i test passati e quelli qui descritti sono completamente differenti4 .
Questo rende il confronto fra i risultati ottenuti solo indicativo. Tuttavia, alcune
delle teorie utilizzate devono essere omesse completamente dall’analisi dei tempi
di esecuzione totale (non dall’analisi dei tempi di esecuzione dei vari metodi) per
i motivi illustrati nel capitolo 3.
Le teorie utilizzate sono: Crypt, Poly, Qsort, Queens, Query, Tak, Einstein’s
riddle, Spanning tree e Switch square. Le prime sei fra queste sono teorie che
erano gi` state usate come benchmark, mentre le altre sono state introdotte so-
a
lamente ora. Einstein’s riddle, Spanning tree e Switch square permettono inoltre
di ingrandire l’albero di risoluzione delle teorie modificando l’obiettivo. In questo
Il sistema su cui sono stati ricavati i risultati antecedenti a quelli qui descritti era equipaggiato
4
con un processore Pentium III 800 MHz, 256 MB di RAM su Windows 2000 e J2SE 1.5.0 09. Il
sistema attuale consiste in un processore Pentium IV 2.6 GHz, con 1 GB di RAM su Windows
XP Home Edition e J2SE 1.6.0 04.
11
22. CAPITOLO 2. BENCHMARKS
modo ` possibile incrementare o diminuire il lavoro svolto dal motore tuProlog
e
senza modificare la teoria in s´, consentendo di analizzare la scalabilit` del siste-
e a
ma. Esse svolgono il seguente lavoro: Crypt, data una formula matematica nota,
ne cerca la soluzione; Poly dato un polinomio nella forma 1 + x + y + z lo eleva
alla decima potenza attraverso la trasformazione simbolica della formula; Qsort
riordina una lista di numeri in modo crescente; Queens ` la versione estesa a nove
e
regine del pi` famoso problema delle 8 regine [1]; Query interroga un database
u
contenente per ogni nazione la rispettiva dimensione e il numero di cittadini e
restituisce, conoscendo gi` il nome di una nazione, quella con la densit` pi` simile
a au
ad essa; Einstein’s riddle tenta di risolvere un indovinello nel quale viene chiesto
chi ` il proprietario di un certo animale data una serie di indizi riguardo a cinque
e
persone di diversa nazionalit`, residenti in case di colore diverso e con una distinta
a
preferenza in termini di animali domestici, marche di sigarette e bevande; Span-
ning tree data una lista di collegamenti fra nodi, ognuno con un certo peso, trova
appunto lo spanning tree [1] di quell’albero; Switch square data una matrice 3x3,
i cui valori possono essere vero o falso, tenta di negare i valori posizionati ad una
distanza di Manhattan uguale o inferiore ad 1 da una certa cella e, attraverso una
sequenza di commutazioni delle celle, cerca di rendere tutti i valori della matrice
veri.
12
23. Capitolo 3
Analisi dei tempi di esecuzione
In questo capitolo vengono presentati i risultati ottenuti dal profiling delle
teorie di test esposte precedentemente. In particolare vengono mostrati quali sono
i tempi di esecuzione totali delle teorie e i tempi di esecuzione dei vari metodi
utilizzati durante la loro risoluzione. I dati ottenuti vengono poi elaborati in modo
da mettere in evidenza quali sono i punti fondamentali su cui il motore deve essere
migliorato. Per fare ci` viene prima messa alla prova la scalabilit` del sistema,
o a
facendo il profiling con teorie via via pi` complesse ed in secondo luogo vengono
u
costruiti i grafi delle chiamate ai metodi, in modo da evidenziare quali metodi
effettuano le chiamate pi` costose da un punto di vista computazionale.
u
3.1 Metodo di profiling
Come gi` detto, i dati risultanti dal profiling devono fornire il tempo di ese-
a
cuzione totale della teoria, il tempo di esecuzione dei metodi e, indirettamente,
per ognuno di essi, i tempi di esecuzione dei metodi innestati. Ci` ` possibile in
oe
quanto il profiler HPROF mette a disposizione il trace dello stack che ha generato
la chiamata ad un particolare metodo, oltre ovviamente al tempo di esecuzione del
metodo (mostrato come percentuale sul tempo di esecuzione totale) e al numero
di volte in cui ` stato chiamato. Il comando utilizzato per fare ci` ` il seguente
e oe
java -agentlib:hprof=cpu=times alice.tuprolog.Agent teoria.pl
13
24. CAPITOLO 3. ANALISI DEI TEMPI DI ESECUZIONE
dove la classe Agent, contenuta nel package alice.tuprolog, ` uno strumento che
e
accetta come parametro una teoria Prolog scritta in un file di testo e, dopo aver
tentato di risolverla, stampa sullo standard output il risultato della valutazione.
tuProlog permette di valutare una teoria in altri tre modi: lanciando un’interfaccia
grafica ed inserendo la teoria in un’apposita TextBox, in un ambiente interattivo
attraverso la classe alice.tuprologx.ide.CUIConsole oppure istanziando un oggetto
Prolog in una classe Java. Il metodo utilizzato, a differenza degli altri, permette
per` di isolare il motore tuProlog, in modo da non dover estromettere dal profiling
o
i dati non dovuti alla risoluzione, come ad esempio l’istanziazione della GUI.
Per misurare il tempo di esecuzione in modo ottimale il file teoria.pl viene
costruito in modo da separare tre diversi processi che avvengono durante la riso-
luzione della teoria: l’inizializzazione del motore e il caricamento delle librerie1 ,
la lettura delle clausole di Horn e la risoluzione vera e propria. Vengono quin-
di dapprima misurati i tempi di esecuzione della lettura della teoria assieme alla
sua risoluzione risolvendo un certo obiettivo2 . Successivamente lo stesso obiet-
tivo viene risolto per undici volte e viene sottratto il tempo di esecuzione pre-
cedentemente trovato. Questo ` equivalente a misurare undici volte il tempo di
e
esecuzione dell’obiettivo pi` una volta il tempo di lettura della teoria per poi sot-
u
`
trarlo. E inoltre necessario evitare di misurare il tempo di creazione della JVM
e per fare ci` viene scritta un’ulteriore teoria in cui viene usato il metodo Ja-
o
va java.lang.System.currentTimeMillis prima e dopo l’esecuzione dell’obiettivo. Il
codice Prolog della teoria indicata risulta il seguente:
time(T) :- class(’java.lang.System’) <- currentTimeMillis returns T.
go(File) :- time(A), consult(File), time(B), C is B-A, print(C).
dove File rappresenta il nome completo del file di testo in cui ` contenuta la
e
teoria da valutare. All’interno di questo file sono contenute le seguenti clausole, le
quali permettono di valutare il tempo di lettura ed esecuzione:
I tempi in cui questi processi avvengono sono dovuti alla JVM e non al motore tuProlog in
1
s´.
e
Per diminuire la dispersione delle misure ` stato misurato dodici volte il tempo di esecuzione
e
2
dell’obiettivo e sottratto il valore massimo e minimo trovato. Il tempo di esecuzione ` la media
e
dei valori rimanenti.
14
25. 3.2. TEMPI DI ESECUZIONE DEI METODI
:- solve(goAndRepeat).
:- solve(go).
goAndRepeat :- goN(11).
go :- goal().
goN(0).
goN(Z) :- goal(), W is Z-1, goN(W).
dove goAndRepeat risolve undici volte l’obiettivo goal().
3.2 Tempi di esecuzione dei metodi
Nonostante il metodo java.lang.System.currentTimeInMillis restituisca un tem-
po in millisecondi, la granularit` del valore dipende dal sistema in cui ` ospitata
a e
la JVM e potrebbe essere maggiore di 1 ms. Nel sistema in cui sono avvenute le
misure non ` stato possibile misurare intervalli di tempo minori a 15 ms, il che
e
fa pensare che lo stesso valore corrisponda alla granularit` del sistema. Questo
a
ha reso impossibile un’apprezzabile misurazione delle teorie Crypt, Poly, Qsort,
Queens e Query in quanto i tempi misurati contengono un errore sulla misura
troppo elevato.
Teoria Tempo di esecuzione (ms) Errore relativo %
Crypt 1669 179,75
Einstein’s riddle 114598 2,62
Poly 31 9677,42
Qsort 671 447,09
Queens 2574 116,55
Query 312 961,54
Spanning tree 18034 16,64
Switch square 93787 3,20
Tak 105721 2,84
Tabella 3.1: Tempi di esecuzione per cento risoluzioni e errore relativo calcolato
per una singola esecuzione.
Dalla Tabella 3.1 si evince chiaramente che la misurazione dei tempi di let-
tura della teoria e risoluzione dell’obiettivo non possono essere misurati con una
15
26. CAPITOLO 3. ANALISI DEI TEMPI DI ESECUZIONE
buona precisione. Nella Tabella 3.2 vengono quindi illustrati i tempi delle teorie
Einstein’s riddle, Spanning tree, Switch square e Tak i quali hanno errori relativi
ragionevolmente contenuti. La distinzione fra le due modalit` di esecuzione ha
a
lo scopo di mettere in luce quali sono le parti del motore tuProlog che possono
causare una degradazione delle performance.
Teoria Lettura esec (ms) Esec (ms) Esec/Lettura esec %
Einstein’s riddle 13807 13331 96,6
Spanning tree 2216 1868 84,3
Switch square 10110 9628 95,2
Tak 11373 10797 94,9
Tabella 3.2: Tempi di lettura ed esecuzione.
Si pu` quindi affermare che il tempo di lettura risulta essere all’incirca costante al
o
variare della teoria3 . I metodi coinvolti nella risoluzione dell’obiettivo occuperan-
no la maggior parte del tempo di esecuzione totale e questo permette un profiling
senza distinguere fra lettura ed esecuzione, come ` stato fatto fin d’ora, dato che
e
non valutare i metodi coinvolti durante la prima fase non comporta una perdita
di informazione significativa.
Di seguito, nella Tabella 3.3, vengono presentati i risultati del profiling, ese-
guiti nei modi descritti precedentemente. Per ogni teoria ci si limita a presentare
i risultati di quei metodi il cui tempo impiegato a terminare4 ` superiore o uguale
e
al 2% del tempo di esecuzione totale.
Nel caso di Spanning tree, la teoria pi` lunga, il numero di righe che il motore deve leggere
u
3
` 186, mentre nel caso di Tak, quella pi` corta, il numero di righe ` 29.
e u e
Il tempo impiegato ad un metodo per terminare non comprende il tempo impiegato ai metodi
4
innestati in esso ad essere eseguiti.
16
29. 3.2. TEMPI DI ESECUZIONE DEI METODI
alice.tuprolog.Var.copy 2,47 1193928
java.util.AbstractList$Itr.<init> 2,35 3388196
java.util.ArrayList.add 2,23 2210364
alice.tuprolog.Var.rename 2,13 645368
alice.tuprolog.Int.unify 2,12 1355254
Tabella 3.3: Analisi dei metodi.
Come si pu` notare, in tre delle nove teorie, il metodo che impiega pi` tempo a
o u
terminare ` java.lang.Object.wait. Questo metodo viene lanciato dalla JVM (nel
e
caso si tratti del client HotSpot della JVM e l’algoritmo usato sia seriale) e per-
mette al garbage collector di ripulire parte dell’heap quando non ` pi` possibile
eu
promuovere oggetti dallo young generation all’old generation [4]. Una spiegazione
pi` approfondita a riguardo viene data nel capitolo seguente. Per tutti gli altri
u
metodi risulta difficile fare una ragionevole analisi basata su di una singola teo-
ria e, come detto nei capitoli precedenti, si deve ricercare un approccio che possa
mettere in luce i miglioramenti in un ambito di utilizzo il pi` eterogeneo possibile.
u
Nella Tabella 3.4 vengono sommati i tempi di esecuzione relativi di tutti i metodi
(anche quelli non inclusi nella Tabella 3.3). Nell’aggregare i risultati delle varie
teorie ` stato scelto di sommare semplicemente i risultati parziali senza dare alcun
e
peso ai tempi di esecuzione delle teorie. Infatti, come si pu` vedere nell’analisi
o
della scalabilit`, modificando l’obiettivo di una teoria, in modo da allungare o ac-
a
corciare l’albero di ricerca di una soluzione, il tempo per ottenere una risposta,
rispettivamente, cresce o diminuisce linearmente con il numero di foglie dell’albero
visitate dalla macchina a stati finiti. Tuttavia i tempi di esecuzione relativi dei
vari metodi rimangono costanti. Per questo motivo dare un peso a questi tem-
pi significherebbe sbilanciare l’analisi complessiva a favore di una teoria anzich´ e
un’altra. Per la stessa ragione nella Tabella 3.4 viene omesso il numero di chiamate
ai metodi.
Sempre dalla Tabella 3.4 si evince che il metodo java.lang.Object.wait ` ancora
e
quello che occupa la maggior parte del tempo di esecuzione del motore tuProlog.
19
30. CAPITOLO 3. ANALISI DEI TEMPI DI ESECUZIONE
Nome metodo Tempo esec %
java.lang.Object.wait 12,15
alice.tuprolog.Var.occurCheck 3,70
alice.tuprolog.Struct.getTerm 3,06
alice.tuprolog.Var.unify 3,05
java.lang.AbstractStringBuilder.append 2,82
alice.tuprolog.Var.free 2,61
alice.tuprolog.Struct.unify 2,25
java.util.AbstractList$Itr.next 1,88
java.util.AbstractList$Itr.hasNext 1,80
alice.tuprolog.Var.getTerm 1,72
java.util.ArrayList.get 1,72
java.lang.StringBuffer.append 1,67
java.util.ArrayList.<init> 1,49
java.util.ArrayList.add 1,37
java.io.StringReader.read 1,16
java.util.AbstractList$Itr.<init> 1,08
alice.tuprolog.Tokenizer.readNextToken 1,01
alice.tuprolog.Var.copy 1,01
Tabella 3.4: Analisi dei metodi totale.
Eliminando, o limitando, il numero di chiamate ad esso comporterebbe un miglio-
ramento del tempo di esecuzione di circa il 12% sul tempo totale5 . Purtroppo,
escludendo il metodo Java gi` citato, non esistono situazioni in cui l’utilizzo di
a
una funzione ` predominante rispetto alle altre, anche se i metodi nella Tabel-
e
la 3.4 rappresentano circa la met` del tempo di esecuzione totale. Questo dato
a
indirizza sicuramente ad una pi` attenta analisi di questi metodi.
u
3.3 Analisi della scalabilit`
a
In questa parte della dissertazione viene data una dimostrazione di come la
relazione fra numero di stati visitati nell’albero di ricerca e numero di chiamate ai
metodi utilizzati sia lineare. Per fare ci` ` opportuno prendere come teoria di rife-
oe
Questa ipotesi, se pur plausibile, non ` del tutto veritiera. A causa della lentezza del profiler
e
5
HPROF non ` stato infatti possibile analizzare teorie di una certa complessit` computazionale e
e a
il valore di miglioramento potrebbe essere sottostimato.
20
31. `
3.3. ANALISI DELLA SCALABILITA
rimento la teoria Switch square che permette di ingrandire o rimpicciolire l’albero
di ricerca solamente modificando l’obiettivo e calcolare anche il numero di stati in
cui si deve transitare per trovare una soluzione. La Tabella 3.5 mostra per ogni
obiettivo valutato il tempo di esecuzione della teoria6 e i cinque metodi con tempo
di esecuzione pi` elevato con il relativo numero di chiamate per quel metodo.
u
N. stati e Tempo esec (ms) Nome metodo N. chiamate
alice.tuprolog.Struct.unify 1947041
alice.tuprolog.Var.unify 2584249
3 + 6! 247837 alice.tuprolog.Var.free 1802505
java.util.ArrayList.get 2639270
java.util.AbstractList$Itr.next 1725777
alice.tuprolog.Struct.unify 3876572
alice.tuprolog.Var.unify 5181011
3 + 2 ∗ 6! 488623 alice.tuprolog.Var.free 3601750
java.util.AbstractList$Itr.next 3449996
java.util.ArrayList.get 5276156
java.lang.Object.wait 2
java.lang.ref.ReferenceQueue.remove 4
alice.tuprolog.Struct.unify 5813154
3 + 3 ∗ 6! 1898223 alice.tuprolog.Var.unify 7768221
alice.tuprolog.Var.free 5398379
java.util.AbstractList$Itr.next 5173233
java.util.ArrayList.get 7911406
Tabella 3.5: Analisi della scalabilit` del motore tuProlog.
a
La Tabella 3.5, oltre a mostrare la linearit` fra stati visitati e numero di chia-
a
mate ad un metodo, mostra anche come il tempo di esecuzione risulti lineare in
rapporto all’aumentare dei nodi nell’albero di risoluzione. Questo dimostra una
buona scalabilit` del sistema fino alla risoluzione dell’obiettivo con 3 + 2 ∗ 6! stati,
a
mentre le prestazioni degradano nella risoluzione dell’obiettivo con 3 + 3 ∗ 6! stati
a causa dell’intervento del garbage collector.
Si tenga in considerazione che il tempo di esecuzione ` pregiudicato dal profiler HPROF che
e
6
rallenta l’esecuzione di tuProlog. Per questo motivo non deve essere confrontato con quello della
Tabella 3.3, ma solamente con quello degli altri obiettivi.
21
32. CAPITOLO 3. ANALISI DEI TEMPI DI ESECUZIONE
3.4 Grafo delle chiamate
Con lo scopo di analizzare pi` in dettaglio i singoli metodi con i tempi di esecu-
u
zione pi` elevati (si veda la Tabella 3.4) vengono esaminate le chiamate ad ognuno
u
dei metodi stessi. Dai risultati di questa analisi ` possibile comprendere se l’elevato
e
tempo di esecuzione di una funzione ` dovuto all’implementazione della funzione
e
stessa oppure all’uso troppo intenso della funzione da parte di altri metodi. Di
conseguenza ` possibile orientare la riscrittura del codice in una delle due dire-
e
zioni. Per lo stesso principio che ha portato all’aggregazione dei risultati parziali
delle varie teorie ed esposto durante la discussione sull’analisi della scalabilit`, lo
a
studio ` stato realizzato esaminando le chiamate complessivamente pi` utilizzate,
e u
senza dare peso ai tempi di esecuzione delle singole teorie. Per ogni singolo metodo
presente nella Tabella 3.4 ` stato esplorato lo stack allo scopo di trovare quale fu
e
la prima funzione del package alice a generarne l’esecuzione. Questo accorgimento
` dovuto al fatto che solamente il codice dei metodi del package alice pu` essere
e o
riscritto, quindi le chiamate fra metodi java (senza tenere conto del fatto che sono
da considerarsi gi` ottimizzate) non aggiungono informazione all’analisi7 .
a
Il risultato dello studio ` la Figura 3.1, la quale mostra per ogni metodo,
e
rappresentato come nodo nel grafo (si faccia riferimento alla Tabella 3.6 per cono-
scere a quale metodo corrisponde ogni nodo), quali sono i suoi chiamanti attraverso
una freccia entrante. Una freccia uscente ed entrante nello stesso nodo indica una
chiamata ricorsiva e per ognuna delle frecce viene indicata in quale percentuale
un determinato metodo viene eseguito per causa del chiamante sul totale delle
chiamate8 .
L’unico metodo, inserito nella Figura 3.1, il cui chiamante fa parte del package java `
e
7
java.lang.Object.wait.
Vengono visualizzati i chiamanti con percentuali uguali o superiori al 5%.
8
22
33. 3.4. GRAFO DELLE CHIAMATE
N. nodo Nome metodo
0 java.io.StringReader.read
1 alice.tuprolog.Var.unify
2 java.util.ArrayList.add
3 alice.tuprolog.Var.getTerm
4 alice.tuprolog.Struct.unify
5 alice.tuprolog.Var.occurCheck
6 alice.tuprolog.Var.copy
7 alice.tuprolog.Struct.getTerm
8 alice.tuprolog.Tokenizer.readNextToken
9 java.util.AbstractList$Itr.hasNext
10 alice.tuprolog.Var.free
11 java.lang.Object.wait
12 java.util.ArrayList.get
13 java.util.AbstractList$Itr.<init>
14 java.util.AbstractList$Itr.next
15 java.lang.StringBuffer.append
16 java.lang.AbstractStringBuilder.append
17 java.util.ArrayList.<init>
Tabella 3.6: Associazione fra il numero identificativo di un nodo nella Figura 3.1
e il metodo che esso rappresenta.
23
34. CAPITOLO 3. ANALISI DEI TEMPI DI ESECUZIONE
ClauseStore.deunify
Int.unify
36 11
100
60 9 SubGoalTree.addChild
0 1 2
61 18
Int.unify
13 14
18
40
StateRuleSelection.doJob 25
7 Term.match
3 4 5
24
85
48
11
10 68
99 Tokenizer.readToken
99
Struct.copy
6 7 8
13
Term.match
Struct.copy
9 75
14 100
71 java
15 StateBacktrack.dojob
ClauseStore.deunify 7
9 10 11
Cl
au
se
St
or
e.r
eu
n
ify
18 6
50 75
12 ClauseStore.deunify
java
15 5
SubGoalTree.getChild 13 ClauseStore.deunify
12 13 14
ClauseInfo.bodyCopy
6 12 9
7
8
ify
py
Te
fy
un
r
o
ni
m
yC
eu
.re
.m
od
Var.rename
Var.rename
e.d
re
at
Term.unify
b
to
ch
o.
or
eS
nf
St
us
eI
se
us
a
au
Cl
a
Cl
Cl
42 6
45
7 11
27
Struct.toString Struct.<init> ClauseStore.deunify
17
PrimitiveManager.identify
15 16 17
SubGoalTree.<init> 15
Struct.toString 28 7 6
54
10 5
St
Pr
at
im
eR
ch
at $
iti
Struct.<init>
er er
ul
ve
at
or
Op ag
eS
.m
M
et an
el
an
m
ec
er rM
r
ag
Te
tio
st to
er
n.
gi era
.id
.g
do
en
Re p
J
or O
tif
ob
y
at
er
Op
Figura 3.1: Analisi delle chiamate ai metodi pi` utilizzati nell’esecuzione delle
u
teorie.
24
35. Capitolo 4
Analisi dell’utilizzo della memoria
Le stesse teorie presentate nel Capitolo 2 vengono ora utilizzate nel profiling
del motore con lo scopo di analizzare in quale modo tuProlog sfrutta la memoria
heap. Ad una prima discussione sulle modalit` di studio dell’interprete segue la
a
presentazione dei dati ottenuti, ricavati con il proposito di mettere in evidenza
quali sono gli oggetti la cui istanziazione alloca in modo pi` consistente questa
u
risorsa. A questo fine viene valutata la memoria complessivamente allocata du-
rante la risoluzione di una teoria, senza considerare il fatto che il garbage collector
libera lo spazio occupato da quegli oggetti non referenziati in un certo momento
dell’esecuzione. Infine, come nel capitolo precedente, vengono analizzati quali so-
no i metodi che sfruttano maggiormente la memoria, con lo scopo di rendere la
riscrittura del codice del motore il pi` circoscritta e proficua possibile.
u
4.1 Metodo di profiling
Il profiling del motore tuProlog fornisce i dati riguardanti l’utilizzo della memo-
ria ed in particolare, per permettere il tipo di analisi introdotta precedentemente,
deve mettere a disposizione, per ogni oggetto, i dati relativi alla memoria com-
plessiva da esso allocata e il metodo che alloca quel oggetto. Il profiler HPROF
permette di ricavare queste informazioni attraverso il seguente comando:
java -agentlib:hprof=heap=sites,depth=16 alice.tuprolog.Agent tr.pl
25
36. CAPITOLO 4. ANALISI DELL’UTILIZZO DELLA MEMORIA
dove l’opzione hprof=heap=sites visualizza, per ogni metodo incluso in un
certo trace dello stack e per ogni oggetto da esso istanziato, i seguenti dati: il tra-
ce dello stack che ha generato la chiamata al metodo; la quantit` di memoria totale
a
istanziata da quel determinato trace insieme al numero totale di istanze create del-
l’oggetto in questione. Si deve precisare che HPROF non fornisce la quantit` di a
memoria totale occupata da un oggetto, ma bens` la quantit` di memoria occupata
ı a
da esso escludendo lo spazio utilizzato dalle istanze di oggetti generati al suo in-
terno. In questo modo ` possibile affrontare un’analisi classe per classe, separando
e
ognuna di esse da parte del suo contenuto. Se si volesse studiare quanto una classe
sfrutta complessivamente la memoria sarebbe sempre possibile farlo analizzando lo
stack. L’opzione depth=16 serve invece ad aumentare la profondit` dello stack vi-
a
sualizzato in output. Dato che si vogliono studiare quali sono i metodi del package
alice che causano l’istanziazione di un oggetto, anche indirettamente, ` ragione-
e
vole impostare una profondit` del trace uguale a sedici per intercettare tutte le
a
chiamate, se pur a scapito di un profiling pi` lento. Come spiegato nell’analisi dei
u
tempi di esecuzione la classe Agent accetta tr.pl come parametro (in questo caso
` il file di testo contente una delle nove teorie gi` presentate) e viene preferita alle
e a
altre modalit` di esecuzione di tuProlog in quanto permette di analizzare solo la
a
parte del motore che ha il compito di risolvere la teoria.
4.2 Allocazione degli oggetti in memoria
L’allocazione degli oggetti, oltre ad incrementare la quantit` di memoria di cui
a
tuProlog deve disporre per risolvere una teoria, porta anche ad un aumento dei
tempi di esecuzione. Questo avviene sia a causa del tempo necessario alla JVM a
creare una nuova istanza di un oggetto in memoria, sia a causa del tempo neces-
sario al garbage collector per ripulire lo spazio non referenziato da alcun oggetto.
Inoltre l’uso improprio di questa risorsa pu` causare il lancio dell’errore OutOf-
o
MemoryError1 , interrompendo la risoluzione della teoria.
La Tabella 4.1 ` stata costruita sommando le quantit` di memoria istanzia-
e a
Questo errore viene lanciato da parte della virtual machine nel momento in cui, a causa della
1
mancanza di spazio in memoria, non ` pi` possibile allocare un certo oggetto.
eu
26
37. 4.2. ALLOCAZIONE DEGLI OGGETTI IN MEMORIA
te dai vari trace per lo stesso oggetto durante l’esecuzione di tuProlog. In questo
modo ` possibile valutare complessivamente quanto un determinato oggetto viene
e
istanziato. Inoltre vengono omessi quegli oggetti che occupano uno spazio in me-
moria inferiore al 2% dello spazio totale allocato.
Teoria Nome oggetto M. allocata % M. allocata Byte
java.lang.Object[] 26,09 1631496
char[] 15,44 965728
java.util.ArrayList 8,54 534280
java.util.AbstractList$Itr 7,68 480040
alice.tuprolog.Struct 5,80 362712
Crypt
java.lang.String 4,83 302128
alice.tuprolog.Term[] 4,37 273128
alice.tuprolog.Var 3,85 240728
byte[] 3,71 231960
java.lang.StringBuffer 2,32 145080
java.lang.Object[] 41,27 87078352
char[] 10,15 21423200
java.util.ArrayList 9,92 20935528
java.util.AbstractList$Itr 8,49 17917864
alice.tuprolog.Var 3,88 8191384
E.’s riddle
alice.tuprolog.Struct 3,26 6870200
java.util.AbstractList$ListItr 3,22 6787896
java.lang.String 2,94 6207184
java.util.LinkedList$Entry 2,68 5652544
alice.tuprolog.Term[] 2,41 5087632
char[] 33,16 798736
java.util.LinkedList$Entry 13,36 321784
byte[] 12,04 290000
java.lang.String 6,47 155728
Poly
int[] 4,02 96728
alice.tuprolog.Token 3,17 76312
27
40. CAPITOLO 4. ANALISI DELL’UTILIZZO DELLA MEMORIA
alice.tuprolog.ExecutionContext 2,03 8132024
Tabella 4.1: Analisi degli oggetti utilizzati.
Come si pu` osservare, in otto delle nove teorie, l’oggetto pi` utilizzato ` un
o u e
array di Object. Esso viene utilizzato principalmente nella classe ArrayList per
mantenere un buffer nel quale viene memorizzata la lista in questione. Un altro
oggetto che compare vistosamente nella tabella ` l’array char[], che viene utilizzato
e
prevalentemente per memorizzare delle stringhe nelle classi String e StringBuilder
(o nella sua versione synchronized StringBuffer). Nel grafo delle allocazioni si pu` o
vedere in modo pi` specifico quali sono i metodi che istanziano questi oggetti.
u
Nella Tabella 4.3 vengono mostrati quali sono gli oggetti complessivamente
pi` utilizzati durante la risoluzione delle teorie. Per le stesse ragioni esposte nel-
u
l’analisi della scalabilit` le varie percentuali di utilizzo della memoria degli oggetti
a
vengono sommate dando lo stesso peso alle varie teorie, sebbene la quantit` di a
memoria totale istanziata da una di esse possa essere diversa da quella istanziata
dalle altre. A sostegno di questa tesi, nella Tabella 4.2, vengono elencati i sei og-
getti che hanno fatto misurare le percentuali di utilizzo della memoria pi` elevate
u
durante la risoluzione della teoria Switch square. Le misurazioni sono state fatte
per ognuno dei tre obiettivi utilizzati nell’analisi dei metodi, i quali permettono
di cercare in 3 + 6!, in 3 + 2 ∗ 6! oppure in 3 + 3 ∗ 6! diversi stati prima di tro-
vare la soluzione della teoria. Anche se la precisione dei risultati non rispecchia
quella avuta nell’analisi dei tempi di esecuzione, si pu` comunque assumere, con
o
buona approssimazione, che al variare dell’obiettivo e del numero di stati visitati
nell’albero di ricerca la percentuale di spazio allocato in memoria da ogni oggetto,
rispetto allo spazio totale allocato, rimane costante. Come detto precedentemente,
questo permette di analizzare l’utilizzo della memoria semplicemente sommando i
dati contenuti nella Tabella 4.1.
30
41. 4.2. ALLOCAZIONE DEGLI OGGETTI IN MEMORIA
N. stati Nome oggetto M. allocata %
java.lang.Object[] 24,74
alice.tuprolog.Struct 17,78
char[] 16,87
3 + 6!
alice.tuprolog.Term[] 12,56
alice.tuprolog.Var 6,66
java.lang.String 5,05
alice.tuprolog.Struct 23,23
char[] 17,80
alice.tuprolog.Term[] 17,31
3 + 2 ∗ 6!
alice.tuprolog.Var 12,60
java.lang.String 10,97
java.lang.Object[] 8,87
alice.tuprolog.Struct 19,12
char[] 18,37
alice.tuprolog.Term[] 13,94
3 + 3 ∗ 6!
java.lang.Object[] 9,97
java.lang.String 9,78
alice.tuprolog.Var 9,37
Tabella 4.2: Analisi dell’utilizzo della memoria da parte degli oggetti rispetto
all’obiettivo della teoria.
Infine, nella Tabella 4.3, vengono esposti i risultati di questa operazione omet-
tendo gli oggetti che allocano meno dell’1% della memoria totale istanziata. L’a-
nalisi conferma quanto detto precedentemente. Gli oggetti Object[] e char[] sono
ancora i pi` utilizzati da parte di tuProlog occupando il 42,11% della memoria
u
totale impiegata. Inoltre, ricordando che il profiler HPROF non ingloba nella mi-
sura della memoria allocata da un oggetto lo spazio occupato da oggetti innestati,
` possibile considerare questi due oggetti istanziati solamente dalle classi Array-
e
List, String e StringBuffer. Seguendo questa ipotesi le classi String e StringBuffer
allocano complessivamente il 20,68% della memoria, mentre la classe ArrayList ne
alloca il 35,30%. Sar` quindi importante, al momento di una futura implemen-
a
tezione del codice, considerare queste tre classi, le quali occupano nell’insieme il
55,98% della memoria totale.
31