11
RouteSharp PathFinder: Un’applicazione Microsoft F# per la ricerca di cammini minimi in una rete complessa mediante l’algoritmo di ricerca A* Davide G. Monaco, Andrea Tino Prof. Ing. A. Faro @ DIIEI UNICT Marzo 2011 1 Introduzione generale RouteSharp ` e un’applicazione Microsoft .NET per l’acquisizione di reti complesse e la ricerca di cammini minimi su esse. L’intera applicazione si divide in due grandi unit` a: 1. PathFinder Si tratta del core di sistema scritto in F# in grado di caricare reti complesse da file (tramite un microlinguaggio usato per la specifica dei nodi, delle connessioni e delle query) e fornire API per la ricerca dei cammini minimi. 2. RouteSharp Si tratta di tutta l’applicazione comprensiva del core in F# e dei futuri moduli di interfaccia scritti in C#. 1.1 Funzionalit` a implementate L’applicazione attualmente sviluppata consta del solo core in F# che mette a dispo- sizione le seguenti funzionalit` a: 1. Definizione delle reti: E’ possibile scrivere da file la rete complessa da cari- care. I file (con estensione .rs denominati in maniera pi` u specifica netfile ) vengono scritti tramite un microlinguaggio line-based e permettono di definire i nodi e le connessioni della rete. E’ inoltre possibile definire le query di ricerca negli stessi netfile. 2. Caricamento e traduzione: I file vengono caricati dall’applicazione e tradotti in strutture interne che concorreranno alla generazione dei nodi e del loro vicinato. 3. Net traversal: E’ possibile, dalle query definite, effettuare la ricerca di cammini minimi all’interno della rete caricata. La ricarca avviene tramite algoritmo A*. 4. Modalit` a di interazione: L’applicazione, il core in F#, dialoga tramite riga di comando. E’ possibile specificare diverse opzioni in esecuzione tra le quali anche una modalit` a interattiva mediate la quale il netfile viene esaminato, le query ignorate, e la rete viene dunque costruita e all’utente viene infine richiesto di definire, di volta in volta, lo start point e l’end point del percorso da cercare. 1

Microsoft .NET F# Implementation of A* search algorithm

Embed Size (px)

DESCRIPTION

Final report for a university project assignment at the department of Artificial Intelligence (UNICT). The document is about performance evaluation of an algorithm written in Microsoft .NET F# implementing the A* algorithm. The algorithm is intended to be used for finding shortest paths on city maps given a start and a finish position. Performance comparison is performed with an existing algorithm performing exhausting-naive graph search written in Prolog.

Citation preview

Page 1: Microsoft .NET F# Implementation of A* search algorithm

RouteSharp PathFinder: Un’applicazione Microsoft F# per

la ricerca di cammini minimi in una rete complessa

mediante l’algoritmo di ricerca A*

Davide G. Monaco, Andrea TinoProf. Ing. A. Faro @ DIIEI UNICT

Marzo 2011

1 Introduzione generale

RouteSharp e un’applicazione Microsoft .NET per l’acquisizione di reti complesse e laricerca di cammini minimi su esse. L’intera applicazione si divide in due grandi unita:

1. PathFinder Si tratta del core di sistema scritto in F# in grado di caricare reticomplesse da file (tramite un microlinguaggio usato per la specifica dei nodi, delleconnessioni e delle query) e fornire API per la ricerca dei cammini minimi.

2. RouteSharp Si tratta di tutta l’applicazione comprensiva del core in F# e deifuturi moduli di interfaccia scritti in C#.

1.1 Funzionalita implementate

L’applicazione attualmente sviluppata consta del solo core in F# che mette a dispo-sizione le seguenti funzionalita:

1. Definizione delle reti: E’ possibile scrivere da file la rete complessa da cari-care. I file (con estensione .rs denominati in maniera piu specifica netfile) vengonoscritti tramite un microlinguaggio line-based e permettono di definire i nodi e leconnessioni della rete. E’ inoltre possibile definire le query di ricerca negli stessinetfile.

2. Caricamento e traduzione: I file vengono caricati dall’applicazione e tradottiin strutture interne che concorreranno alla generazione dei nodi e del loro vicinato.

3. Net traversal: E’ possibile, dalle query definite, effettuare la ricerca di camminiminimi all’interno della rete caricata. La ricarca avviene tramite algoritmo A*.

4. Modalita di interazione: L’applicazione, il core in F#, dialoga tramite rigadi comando. E’ possibile specificare diverse opzioni in esecuzione tra le qualianche una modalita interattiva mediate la quale il netfile viene esaminato, le queryignorate, e la rete viene dunque costruita e all’utente viene infine richiesto didefinire, di volta in volta, lo start point e l’end point del percorso da cercare.

1

Page 2: Microsoft .NET F# Implementation of A* search algorithm

2 Introduzione al paradigma funzionale

La programmazione funzionale e un paradigma di programmazione in cui la computazioneviene trattata come la valutazione di funzioni matematiche, evitando l’uso stati e datimutabili. Le origini della programmazione funzionale possono essere ricondotte al lambdacalcolo ed alla ricorsione.

La programmazione funzionale pone maggior accento sulla definizione di funzioni,rispetto ai paradigmi procedurali e imperativi, che prediligono la specifica di una se-quenza di comandi da eseguire. In questi ultimi, i valori vengono calcolati cambiandolo stato del programma attraverso delle assegnazioni; un programma funzionale e im-mutabile: i valori non vengono trovati cambiando lo stato del programma, ma costruendonuovi stati a partire dai precedenti.

La differenza tra il concetto di funzione in un linguaggio imperativo ed in un linguag-gio funzionale consiste nel fatto che nel primo una funzione puo avere dei side-effects (oeffetti collaterali), mentre nel secondo no. Cio rappresenta uno dei maggiori vantagginell’uso del paradigma funzionale, in quanto non solo sara molto piu semplice verifi-care la correttezza e la mancanza di bug del programma, ma sara anche piu efficacel’ottimizzazione dello stesso.

Impiego

Tipicamente, come tutti i linguaggi di alto livello, i linguaggi funzionali sono meno effi-cienti in merito all’uso di CPU e memoria rispetto a linguaggi imperativi di piu bassolivello come il C. Ad ogni modo e possibile affermare che per programmi che effettuanocomputazioni numeriche intense, gestiscono grosse matrici o database multidimension-ali, alcuni linguaggi funzionali riescono a raggiungere le prestazioni di poco inferiori alC. Inoltre, l’immutabilita dei dati, in molti casi, aumenta l’efficienza dell’esecuzione,permettendo al compilatore di fare assunzioni che sarebbero poco sicure nel caso di lin-guaggio imperativo.

Si potrebbe quindi pensare che i linguaggi funzionali possano essere impiegati inmodo efficace solamente per scopi puramente accademici.Molti linguaggi funzionali, invece, sono stati utilizzati per scopi commerciali o indus-triali negli ultimi decenni, basti pensare ad Erlang che venne impiegato negli anni 80dalla Ericsson per implementare sistemi di telecomunicazioni fault-tolerant, oppure F#,che essendo incluso nell’ambiente .NET della Microsoft sta trovando impiego in ambitocommerciale.

La decisione di implementare l’algoritmo A* in F# e scaturita dalla volonta di ri-considerare la programmazione funzionale e proporre un’alternativa non presente inletteratura.

3 L’algoritmo A*

A* (A star) e un noto algoritmo per la ricerca su grafi che individua un percorso daun nodo iniziale ad un nodo destinazione, descritto per la prima volta nel 1968 come

2

Page 3: Microsoft .NET F# Implementation of A* search algorithm

estenzione dell’algoritmo di Dijkstra.

3.1 Introduzione

A* utilizza ricerche di tipo best-first per trovare il percorso a costo minore; inoltre,utilizzando una funzione euristica di tipo distance-plus-cost, riesce ad essere moltoperformante e a determinare l’ordine in cui i nodi verranno esplorati. Tale funzione, chechiameremo f(x), viene determinata secondo la relazione:

f(x) = g(x) + h(x)

dove:

- g(x) e una funzione che tiene conto del costo di spostamento all’interno del grafodal nodo di partenza al nodo corrente.

- h(x) e una funzione di stima euristica che calcola la distanza dal nodo corrente alnodo destinazione. Deve essere un’euristica ammissibile, ovvero una funzione chemai sovrastima il costo del raggiungimento della destinazione. Per questo motivo,in genere, soprattutto in ambito routing, la distanza considerata e una distanza”in linea retta” (es. la norma in uno spazio vettoriale).

3.2 Principio

A* attraversa il grafo verso la destinazione seguendo il percorso a minor costo conosciuto,mantenendo ordinata una priority queue di segmenti alternativi lungo la strada. Se,quindi, in un qualsiasi punto un possibile percorso ha un costo superiore rispetto adun altro gia incontrato precedentemente, l’algortmo abbandona il percorso a costo piualto in favore del segmento a costo piu basso. Questo processo viene applicato finche ladestinazione non viene raggiunta. Come accennato precedentemente, il costo preso inesame non e costituito unicamente dal costo del singolo spostamento dal nodo correnteal nodo vicino, bensı considera tutti gli spostamenti effettuati fino all’attuale posizione,g(x), e la distanza in linea retta dalla destinazione, h(x), potendo quindi valutare quantoeffettivamente ci si avvicina alla destinazione con il successivo spostamento.

3.3 Funzionamento

Iniziando dal nodo di partenza, come precedentemente accennato, viene mantenuta unapriority queue, nota convenzionalmente come open set. Il nodo con f-score, valoredi f(x), minore all’interno della coda ha maggiore priorita. Ad ogni step, il nodo conpriorita piu alta viene rimosso dall’open set, vengono calcolati ed annotati gli f-score deisuoi vicini e gli stessi vengono aggiunti all’open set.L’algoritmo continua finche il nodo di destinazione ha un f-score minore di qualsiasialtro nodo nell’open set, ritornando il percorso con successo, o altrimenti finche non visono piu nodi nell’open set, nel qual caso il percorso non esiste.

3.4 Proprieta

A* e un algoritmo completo, quindi siamo sicuri che trovera sempre una soluzione, sequesta esiste. Inoltre, esso e sia ammissibile che ottimo rispetto agli altri algoritmi

3

Page 4: Microsoft .NET F# Implementation of A* search algorithm

di ricerca ammissibili: ha una stima ottimistica del costo del percorso attraverso ogninodo considerato. L’ottimismo consiste anche nel sapere che il vero costo del percorsoattraverso ciascun nodo verso la destinazione varra almeno quanto vale la nostra stima.Quindi la conoscenza di A* e cruciale. Per definizione, quando l’algoritmo ha terminatola sua ricerca avra trovato un percorso il cui costo attuale e piu basso del costo stimatoper ogni percorso attraverso tutti i nodi rimasti nell’open set, essendo sicuri di non avertrascurato alcun percorso dal costo minore, quindi A* e ammissibile.

Se viene utilizzato un closed set per tener traccia dei nodi gia esaminati, incremen-tando le prestazioni dell’algoritmo, la funzione euristica h(x) oltre che ammissibile deveessere monotona, ovvero deve soddisfare la relazione:

h(x) ≤ d(x, y) + h(y)

dove d(x, y) e la distanza tra i nodi x e y.

3.5 Complessita

La complessita di A* dipende dalla funzione euristica utilizzata.Nel caso peggiore, il numero di nodi espansi e esponenziale rispetto alla lunghezza dellasoluzione, ma e polinomiale quando lo spazio di ricerca e un albero, la destinazione e unsingolo nodo e la funzione euristica h(x) soddisfa la seguente relazione:

|h(x)− h∗(x)| = O(log(h∗(x)))

dove h∗(x) e l’euristica ottima, ovvero la reale distanza che intercorre tra il nodo desti-nazione e il nodo x; detto in altri termini, se l’errore della funzione h(x) non cresce piuvelocemente del logaritmo dell’euristica ottima h∗(x).

4 A* in RouteSharp

In RouteSharp l’algoritmo A* e stato inizialmente prototipato in Perl e successivamenteimplementato in F#.

4.1 Implementazione

Essendo F# un linguaggio multiparadigma focalizzato sulla programmazione funzionale,in RouteSharp l’implementazione dell’algoritmo differisce dalla canonica, presentata inletteratura con approccio procedurale. Sono state implementate diverse funzioni, lamaggior parte delle quali ricorsive, al fine di ricoprire tutti gli aspetti implicati nel cor-retto funzionamento dell’algoritmo.Di seguito viene riportato il codice delle funzioni implementate, AStar(), Scan(),Update(), RebuildPath() e PrintPath(), accompagnato da commenti esplicativi perciascuna funzione.

type public Cost = double

type public StarNode = {

Node: NetworkNode;

FCost: Cost;

GCost: Cost;

4

Page 5: Microsoft .NET F# Implementation of A* search algorithm

HCost: Cost;

Parent: NetworkNode option;

}

Sono stati definiti i tipi Cost, mappato sul tipo base double, e StarNode, che in-capsula il nodo corrente nella rete, Node di tipo NetworkNode, e associandogli i valoridi f(x), g(x) e h(x) di cui l’algoritmo ha bisogno. Viene inoltre tenuta traccia del nododi provenienza, Parent per poter ricostruire il percorso una volta trovato. La keywordoption denota che il campo e opzionale e che quindi potrebbe non essere presente.

let public AStar start goal =

let norm_start = Norm (start , goal)

let start1 = {

Node = start;

FCost = norm_start;

GCost = 0.0;

HCost = norm_start;

Parent = None

}

let goal1 = {

Node = goal;

FCost = 0.0;

GCost = 0.0;

HCost = 0.0;

Parent = None

}

let ol = [ start1 ]

let cl = [ ]

Scan ol cl goal1 Map.empty

La funzione AStar() accetta in ingresso due nodi, start e goal, nel nostro casosaranno sempre NetworkNode, calcola la distanza in linea retta tra essi, tramite la fun-zione Norm(), e ne inizializza i relativi StarNode associati, start1 e goal1. Infine,dopo aver inizializzato le due liste ol e cl, rispettivamente open list e closed list, e vieneinvocata la funzione Scan(), passando come parametri la open list, la closed list, loStarNode associato al nodo destinazione ed una mappa vuota.

let rec public Scan ol cl goal come_from =

match ol with

| [] -> (cl , come_from)

| h :: ol_tail ->

if h.Node.Name = goal.Node.Name then

(cl , come_from)

else

let ol2 = ol_tail

let cl2 = h :: cl

let starnodes = List.map (fun (x: NetworkNodeNeighbour) -> {

StarNode.Node = x.Node;

StarNode.FCost = h.GCost + x.Weight;

StarNode.GCost = h.GCost + x.Weight;

5

Page 6: Microsoft .NET F# Implementation of A* search algorithm

StarNode.HCost = 0.0;

StarNode.Parent = None

}) h.Node.Neighbourhood

let (ol3 , cl3 , come_from2) = Update ol2 cl2 starnodes h come_from

Scan ol3 cl3 goal come_from2

La funzione Scan() e una funzione ricorsiva che accetta in ingresso due liste ol ecl, rispettivamente la open e la closed, il nodo destinazione, goal, ed una mappa chepermette di stabilire i rapporti di parentela tra i nodi, come from.Se ol, e una lista vuota, [], viene ritornata una tupla contenente la closed list e lamappa di nodi, (cl, come from). In caso contrario, viene estratto il nodo in testadalla open list, h, ovvero il nodo con f-score migliore, e vengono effettuati dei controlli.Se h corrisponde al nodo di destinazione, viene ritornata la tupla contenente la closedlist e la mappa di nodi; in caso contrario, h viene inserito in closed list, affinche si abbiamemoria del fatto che tale nodo e stato esaminato, e viene creata una lista contenentei suoi vicini; per aggiornare open list, closed list e mappa di nodi, viene chiamata lafunzione Update(), che tornera una tupla contenente i tre elementi richiesti, ed infineviene effettuata la chiamata ricorsiva a Scan().

La funzione Update() e un po’ piu complessa, quindi, per semplificarne la spie-gazione, verra illustrata passo passo.

let rec public Update ol cl neigh wn come_from =

match neigh with

| [] -> (ol , cl, come_from)

| h :: neigh_tail ->

[...]

Update() e una funzione ricorsiva che ritorna una tupla contenente la open list, laclosed list e la mappa di nodi aggiornata in base alle informazioni passate.Accetta in ingresso ol e cl, rispettivamente open e closed list, wn, il nodo corrente,neigh, la lista dei vicini del nodo corrente, e come from, la mappa di nodi gia descrittaprecedentemente.Se la lista dei vicini e vuota, la funzione ritorna la tupla, altrimenti prosegue esaminandoil primo vicino in neigh che chiameremo h.

if List.exists (fun x -> x.Node.Name = h.Node.Name) cl then

Update ol cl neigh_tail wn come_from

else

*(1)

Se il nodo h e presente in closed list passa al vicino successivo chiamando ricorsiva-mente Update(), altrimenti prosegui esaminando h.

-*(1)-

let testG = wn.GCost + (wn.Node.GetWeightToNeighbour (h.Node.Name))

if List.exists (fun x -> x.Node.Name = h.Node.Name) ol then

*(2)

else

let h2 = {

Node = h.Node;

6

Page 7: Microsoft .NET F# Implementation of A* search algorithm

FCost = testG;

GCost = testG;

HCost = h.HCost;

Parent = Some wn.Node

}

let ol2 = h2 :: ol

let ol3 = List.sortBy (fun x -> x.FCost) ol2

let come_from2 = Map.add h.Node.Name wn.Node.Name come_from

Update ol3 cl neigh_tail wn come_from2

Calcola il valore di g(x), testG, sul nodo h.Se h e presente in open list prosegui valutando ulteriori casi, altrimenti incapsula h inuno StarNode e aggiungilo alla open list. Ordina la open list per f-score e aggiornacome from, ponendo wn come predecessore di h. Infine invoca ricorsivamente Updatecon la nuova open list e lista dei nodi.

-*(2)-

let comp_node = List.find (fun x -> x.Node.Name = h.Node.Name) ol

if testG < comp_node.GCost then

let h2 = {

Node = h.Node;

FCost = testG (*h.FCost *);

GCost = testG (*h.GCost *);

HCost = h.HCost;

Parent = Some wn.Node

}

let replacer (x) =

if x.Node.Name = h.Node.Name then

h2

else

x

let ol2 = List.map (fun x -> (replacer x)) ol

let ol3 = List.sortBy (fun x -> x.FCost) ol2

let come_from2 = Map.add h.Node.Name wn.Node.Name come_from

Update ol3 cl neigh_tail wn come_from2

else

let h2 = {

Node = h.Node;

FCost = testG (*h.FCost *);

GCost = testG (*h.GCost *);

HCost = h.HCost;

Parent = h.Parent

}

let replacer (x) =

if x.Node.Name = h.Node.Name then

h2

else

7

Page 8: Microsoft .NET F# Implementation of A* search algorithm

x

let ol2 = List.map (fun x -> (replacer x)) ol

let ol3 = List.sortBy (fun x -> x.FCost) ol2

Update ol3 cl neigh_tail wn come_from

In questo caso abbiamo trovato h nella open list. Confrontiamo il valore di g(x)precedentemente annotato con testG. Se il valore di test e minore, aggiorna i valoriin ol e come from, riordina la open list e invoca ricorsivamente Update(); altrimentiaggiorna solamente come from e invoca sempre Update().

let rec public RebuildPath finalpath nodemap node =

let init lst =

match lst with

| [] -> [node]

| _ -> lst

try

let par = Map.find node nodemap

RebuildPath (par :: (init finalpath )) nodemap par

with

| :? KeyNotFoundException -> finalpath

La funzione RebuildPath() e una funzione ricorsiva che ritorna il percorso.Accetta in ingresso il percorso da ritornare, finalpath, la mappa di nodi elaboratadurante il processamento di A*, nodemap, e il nodo corrente, node.La ricostruzione viene fatta risalendo dalla destinazione al nodo di partenza, valutandoil nodo predecessore annotato nella nodemap.Viene sollevata un’eccezione nel caso in cui il percorso non e stato trovato.

5 Analisi comparativa con un’applicazione Prolog: PathFinder.exe vsTraffic.exe

Per determinare una scala di performance e un ordine di efficienza di A* scritto in F#,e stata condotta una serie di esecuzioni diagnostiche (run delle applicazioni in contesticontrollati) per accertare le attivita svolte da PathFinder nel caricare la rete e nel cercareil cammino minimo. Le stesse attivita sono state condotte su Traffic, un’applicazionescritta in Prolog avente gli stessi obiettivi di PathFinder.

5.1 Perche questa analisi

La possibilita di esaminare le prestazioni di PathFinder tramite un’analisi comparatacon un’applicazione Prolog permette di stabilire un’ordine di grandezza circa le perfor-mance di una’aplicazione scritta tramite un linguaggio multiparadigma (OO + Func-tional) e un’applicazione scritta tramite un linguaggio logico. Considerando inoltre cheA* possiede, attualmente, pochissime implementazioni funzionali e multiparadigma inletteratura, questa analisi mette anche a fuoco potenziali sviluppi di F# nel campo dellereti complesse.

5.1.1 Limiti e considerazioni

Malgrado si tratti di un’analisi comparata condotta mediante rigidi schemi e col maggiorrigore possibile; e necessario puntualizzare che i run controllati e i dati raccolti non

8

Page 9: Microsoft .NET F# Implementation of A* search algorithm

possono essere considerati per la definizione di una precisa tabella delle performance(dalla quale e possibile stabilire, con certezza, quale sia l’applicazione migliore per unacerta caratteristica in esame) a causa dei seguenti motivi:

1. Numero di run: Il numero di esecuzioni controllate non e tale da stabilire unacorretta descrizione generale del comportamento delle due applicazioni.

2. Contesto software: Il contesto software in cui le applicazioni sono state eseguitenon rispecchiava un classico scenario di esame. Ovvero la presenza di processi inbackground di sistema e di altre applicazioni ha inquinato l’ambiente di lavoro eha, pertanto, reso le grandezze in esame, dipendenti da parametri rumorosi.

3. Contesto hardware: La macchina su cui sono state condotte le esecuzioni nonrispecchiava una vera e propria macchina per test controllati.

Per questo motivo si considerino i risultati seguenti come il frutto di un’analisi volta adeterminare le dinamiche generali delle due applicazioni.

5.2 Strumentazione

Al fine di ottenere dati precisi per analisi in profondita delle performance delle dueapplicazioni, coerentemente al contesto software di base (sistema operativo), e statautilizzata un’applicazione Microsoft per il profiling di una sessione di lavoro. MicrosoftWindows Performance Analysis (WPA) e stata scelta come tool principale per l’analisidelle prestazioni sopratutto a fronte della sua perfetta integrazione coi sistemi Windows(il set di tool interagisce perfettamente con le funzionalita di sistema, rendendo la suiteun componente nativo di Windows). Il risultato e la possibilita di ottenere informazionidavvero precise circa l’esecuzione di una qualsiasi applicazione.

5.2.1 Algoritmo di generazione delle reti complesse

Per generare le reti complesse utilizzate nelle sessioni di run, e stato utilizzato l’algoritmodi Watts e Strogatz.

5.3 Sessione di esecuzione a reti dimensione-variata

Questa sessione di esecuzioni ha visto i due programmi competere su reti a dimensionevariabile. Lo scopo e quello di verificare l’andamento delle prestazioni al crescere delnumero dei nodi, mantenendo costante, nei limiti del possibile, la struttura di vicinatodella rete.

5.3.1 Reti caricate

Le reti caricate dai programmi rispecchiano in tutto 11 configurazioni complesse con unnumero di nodi variabile da 50 a 8000. Tramite l’applicazione di uno stress considirevole,al crescere del numero dei nodi, si verifica la risposta delle applicazioni all’avanzamentodi tale complessita.

5.3.2 Risposta dei tempi di esecuzione

I tempi di esecuzione hanno avuto due differenti andamenti:

9

Page 10: Microsoft .NET F# Implementation of A* search algorithm

• PathFinder: L’applicazione ha mostrato un andamento crescente dei tempi. Ladilatazione temporale evidenziata da PathFinder mette in mostra una dipendenzacoerente tra numero di nodi e tempi. La crescita diviene considerevole a partiredai 1000 nodi in su, dove il salto in ordine di grandezza determina una nettaspaccatura nella complessita dele operazioni da eseguire. Tuttavia, l’applicazionerisponde bene ed in maniera controllata e predicibile.

• Traffic: L’applicazione mostra un pattern fortemente irregolare nei tempi di es-ecuzione al crescere del numero dei nodi. Sono presenti inoltre forti sbalzi alcrescere del numero dei nodi. La rete ad 8000 nodi non viene caricata al completoe l’applicazione sperimanta un crash non occasionale (run multipli sulla configu-razione a 8000 nodi mostrano che Traffic interrompe l’esecuzione a seguito dellostesso crash).

5.3.3 Risposta dei tempi di IO

I tempi di IO (vengono presi in esame solamente i file di condifugrazione della rete peri due programmi) hanno avuto due differenti andamenti:

• PathFinder: L’applicazione ha mostrato un andamento crescente a tratti deitempi. La crescita dei tempi di IO, tuttavia, in rapporto ai tempi di computazione,avviene in maniera differente, mettendo in luce la controparte relativa ai tempi diricerca del percorso ottimo (tempo di pura computazione). I dati mostrano, infatti,un andamento crescente delle attivita di IO che, pero, riscontrano significativi in-nalzamenti solamente nella transizione da certi numeri di nodi. Dunque la crescitae si presente, ma non costante, tuttavia regolare. Da notare, il raggiungimento dei7000 e 8000 nodi dove un brusco innalzamento viene rilevato. Si tratta, ed e benesottolinearlo, di un pattern comunque regolare, infatti la curva dei tempi mostravariazioni d’ordine e tratti di non variazione d’ordine susseguirsi regolarmente.

• Traffic: L’applicazione mostra un pattern fortemente irregolare nei tempi di IOal crescere del numero dei nodi. La crescita si mantiene piuttosto regolare fino alraggiungimento dei 1000 nodi, oltre i quali vengono sperimentati sbalzi acuti deitempi. Tale comportamento evidenzia molte irregolarita nell’esecuzione.

5.3.4 Rapporto dei tempi di computazione ed IO

I tempi di IO e i tempi di pura computazione determinano una percentuale sui tempidi esecizione totale per ambedue i programmi. L’esame di tali rapporti mette in luceil comportamento delle due applicazioni circa le loro attivita con il filesystem dandocila possibilita di valutare quante parte dell’esecuzione viene materialmente spesa nelleoperazioni sui file di configrazione:

• PathFinder: L’applicazione ha mostrato un andamento quasi costante del rap-porto computazione/IO. Osservando l’andamento complessivo al crescere del nu-mero dei nodi, si nota come l’applicazione comunque mantenga sempre una per-centuale al di sotto del 25% dedicata alla computazione vera e propria, mentre uncomplementare 75% circa viene sempre dedicato alla gestione delle operazioni sufile. Tale risultato mette in luce la proprieta piu importante di PathFinder: lalentezza crescente, al crescere della rete, e dovuta nettamente ai tempi necessariaffinche la rete possa essere caricata e tradotta.

10

Page 11: Microsoft .NET F# Implementation of A* search algorithm

• Traffic: L’applicazione mette in luce una percentuale estremamente a favore deitempi di computazione, segno del fatto che i tempi di accesso e modifica dei file diconfigurazione, constano di un parte davvero minima nell’esecuzione complessiva.

5.4 Sessione di esecuzione a reti struttura-variata

Questa sessione di esecuzioni ha visto i due programmi competere su reti generate sec-ondo le seguenti distribuzioni:

• Watts e Strogatz: La rete viene generata a partire da una topologia di base e ilprocesso itera i vari nodi generando nuove connessioni con una data probabilita.

• Barabasi: La rete viene generata a partire da una rete triangolare. Alla retevengono dunque aggiunti i vari nodi con un dato numero di connessioni. La prob-abilita che tali connessioni vengano agganciate ai vari nodi e proporzionale al lorovicinato.

• Bernoulli: La rete viene generata a partire da una rete di base con tutti i nodi. Leconnessioni vengono create secondo lo schema stocastico a tentativi di Bernoulli.

Lo scopo e quello di verificare l’andamento delle prestazioni al crescere del connession-ismo per le varie tipologie di reti.

5.4.1 Reti caricate

Le reti caricate dai programmi rispecchiano in tutto 10 configurazioni complesse con unnumero di nodi fissato a 30. Tramite l’applicazione di uno stress considirevole, al cresceredel connessionismo delle reti, si verifica la risposta delle applicazioni all’avanzamento ditale complessita.

5.4.2 Risultati in generale

I run hanno messo in luce un comportamento molto statico dei tempi di esecuzioneda parte di Traffic, mentre una maggiore sensibilita viene riscontrata da PathFinder.Riguardo le percentuali di effettiva computazione ed IO, troviamo sempre una grandedisparita tra le due applicazioni, dove Traffic utilizza sempre non meno del 25% deltempo totale, in operazioni di IO.

5.5 Sessione di esecuzione a reti heuristic-aware

Questa sessione di esecuzioni ha visto i due programmi competere su reti spazialmentecollocate all’interno di un sistema di riferimento bidimensionale. I nodi hanno coordi-nate e lo spazio normato in esame viene considerato per trovare, appunto nella normaeuclidea, l’euristica utilizzabile da A*. In tale contesto si vuole misurare l’efficienza diA* nel raggiungere l’obiettivo quanto prima mediante l’uso delle euristiche.

5.5.1 Risultati in generale

Un notevole abbattimento dei tempi di computazione da parte di A* viene riscontratograzie all’attivazione del meccanismo ad euristica. I tempi di PathFinder si riducononotevolmente e la forbice con Traffic si allarga. Traffic rimane piu lento, considerando ilpuro tempo di computazione (eliminando l’IO complessivo), PathFinder riesce a trovareil cammino minimo in pochi millisecondi.

11