Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Microsoft .NET F# Implementation of A* search algorithm

440 views

Published on

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.

Published in: Technology
  • Be the first to comment

Microsoft .NET F# Implementation of A* search algorithm

  1. 1. 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 20111 Introduzione generaleRouteSharp ` un’applicazione Microsoft .NET per l’acquisizione di reti complesse e la ericerca 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` implementate aL’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` specifica netfile) vengono u 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` di interazione: L’applicazione, il core in F#, dialoga tramite riga a di comando. E’ possibile specificare diverse opzioni in esecuzione tra le quali anche una modalit` interattiva mediate la quale il netfile viene esaminato, le query a 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
  2. 2. 2 Introduzione al paradigma funzionaleLa programmazione funzionale ` un paradigma di programmazione in cui la computazione eviene 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 ` im- emutabile: 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 pu` avere dei side-effects (o oeffetti collaterali), mentre nel secondo no. Ci` rappresenta uno dei maggiori vantaggi onell’uso del paradigma funzionale, in quanto non solo sar` molto pi` semplice verifi- a ucare la correttezza e la mancanza di bug del programma, ma sar` anche pi` efficace a ul’ottimizzazione dello stesso.ImpiegoTipicamente, 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 pi` basso ulivello come il C. Ad ogni modo ` possibile affermare che per programmi che effettuano ecomputazioni numeriche intense, gestiscono grosse matrici o database multidimension-ali, alcuni linguaggi funzionali riescono a raggiungere le prestazioni di poco inferiori alC. Inoltre, l’immutabilit` dei dati, in molti casi, aumenta l’efficienza dell’esecuzione, apermettendo 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# ` scaturita dalla volont` di ri- e aconsiderare la programmazione funzionale e proporre un’alternativa non presente inletteratura.3 L’algoritmo A*A* (A star) ` un noto algoritmo per la ricerca su grafi che individua un percorso da eun nodo iniziale ad un nodo destinazione, descritto per la prima volta nel 1968 come 2
  3. 3. estenzione dell’algoritmo di Dijkstra.3.1 IntroduzioneA* 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) ` una funzione che tiene conto del costo di spostamento all’interno del grafo e dal nodo di partenza al nodo corrente. - h(x) ` una funzione di stima euristica che calcola la distanza dal nodo corrente al e nodo destinazione. Deve essere un’euristica ammissibile, ovvero una funzione che mai sovrastima il costo del raggiungimento della destinazione. Per questo motivo, in genere, soprattutto in ambito routing, la distanza considerata ` una distanza e ”in linea retta” (es. la norma in uno spazio vettoriale).3.2 PrincipioA* 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 gi` incontrato precedentemente, l’algortmo abbandona il percorso a costo pi` a ualto in favore del segmento a costo pi` basso. Questo processo viene applicato finch´ la u edestinazione non viene raggiunta. Come accennato precedentemente, il costo preso inesame non ` costituito unicamente dal costo del singolo spostamento dal nodo corrente eal 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 FunzionamentoIniziando 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 priorit`. Ad ogni step, il nodo con apriorit` pi` alta viene rimosso dall’open set, vengono calcolati ed annotati gli f-score dei a usuoi vicini e gli stessi vengono aggiunti all’open set.L’algoritmo continua finch´ il nodo di destinazione ha un f-score minore di qualsiasi ealtro nodo nell’open set, ritornando il percorso con successo, o altrimenti finch´ non vi esono pi` nodi nell’open set, nel qual caso il percorso non esiste. u3.4 Propriet` aA* ` un algoritmo completo, quindi siamo sicuri che trover` sempre una soluzione, se e aquesta esiste. Inoltre, esso ` sia ammissibile che ottimo rispetto agli altri algoritmi e 3
  4. 4. 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 varr` almeno quanto vale la nostra stima. aQuindi la conoscenza di A* ` cruciale. Per definizione, quando l’algoritmo ha terminato ela sua ricerca avr` trovato un percorso il cui costo attuale ` pi` basso del costo stimato a e uper ogni percorso attraverso tutti i nodi rimasti nell’open set, essendo sicuri di non avertrascurato alcun percorso dal costo minore, quindi A* ` ammissibile. e Se viene utilizzato un closed set per tener traccia dei nodi gi` esaminati, incremen- atando 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) ` la distanza tra i nodi x e y. e3.5 Complessit` aLa complessit` di A* dipende dalla funzione euristica utilizzata. aNel caso peggiore, il numero di nodi espansi ` esponenziale rispetto alla lunghezza della esoluzione, ma ` polinomiale quando lo spazio di ricerca ` un albero, la destinazione ` un e e esingolo nodo e la funzione euristica h(x) soddisfa la seguente relazione: |h(x) − h∗ (x)| = O(log(h∗ (x)))dove h∗ (x) ` l’euristica ottima, ovvero la reale distanza che intercorre tra il nodo desti- enazione e il nodo x; detto in altri termini, se l’errore della funzione h(x) non cresce pi` uvelocemente del logaritmo dell’euristica ottima h∗ (x).4 A* in RouteSharpIn RouteSharp l’algoritmo A* ` stato inizialmente prototipato in Perl e successivamente eimplementato in F#.4.1 ImplementazioneEssendo 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
  5. 5. 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 ` opzionale e che quindi potrebbe non essere presente. elet 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 : N e t w o r k N o d e N e i g h b o u r ) -> { StarNode . Node = x . Node ; StarNode . FCost = h . GCost + x . Weight ; StarNode . GCost = h . GCost + x . Weight ; 5
  6. 6. 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() ` una funzione ricorsiva che accetta in ingresso due liste ol e 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, ` una lista vuota, [], viene ritornata una tupla contenente la closed list e la emappa 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, affinch´ si abbia ememoria del fatto che tale nodo ` stato esaminato, e viene creata una lista contenente ei suoi vicini; per aggiornare open list, closed list e mappa di nodi, viene chiamata lafunzione Update(), che torner` una tupla contenente i tre elementi richiesti, ed infine aviene effettuata la chiamata ricorsiva a Scan(). La funzione Update() ` un po’ pi` complessa, quindi, per semplificarne la spie- e ugazione, verr` illustrata passo passo. a let rec public Update ol cl neigh wn come_from = match neigh with | [] -> ( ol , cl , come_from ) | h :: neigh_tail -> [...] Update() ` una funzione ricorsiva che ritorna una tupla contenente la open list, la eclosed 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 gi` descritta aprecedentemente.Se la lista dei vicini ` vuota, la funzione ritorna la tupla, altrimenti prosegue esaminando eil 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 ` presente in closed list passa al vicino successivo chiamando ricorsiva- emente Update(), altrimenti prosegui esaminando h.-*(1) - let testG = wn . GCost + ( wn . Node . G e t W e i g h t T o N e i g h b o u r ( h . Node . Name )) if List . exists ( fun x -> x . Node . Name = h . Node . Name ) ol then *(2) else let h2 = { Node = h . Node ; 6
  7. 7. 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 ` presente in open list prosegui valutando ulteriori casi, altrimenti incapsula h in euno 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
  8. 8. 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 ` minore, aggiorna i valori ein 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 | :? K e y N o t F o u n d E x c e p t i o n -> finalpath La funzione RebuildPath() ` una funzione ricorsiva che ritorna il percorso. eAccetta 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 ` stato trovato. e5 Analisi comparativa con un’applicazione Prolog: PathFinder.exe vs Traffic.exePer determinare una scala di performance e un ordine di efficienza di A* scritto in F#,` stata condotta una serie di esecuzioni diagnostiche (run delle applicazioni in contestiecontrollati) per accertare le attivit` svolte da PathFinder nel caricare la rete e nel cercare ail cammino minimo. Le stesse attivit` sono state condotte su Traffic, un’applicazione ascritta in Prolog avente gli stessi obiettivi di PathFinder.5.1 Perch` questa analisi eLa possibilit` di esaminare le prestazioni di PathFinder tramite un’analisi comparata acon 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 considerazioniMalgrado si tratti di un’analisi comparata condotta mediante rigidi schemi e col maggiorrigore possibile; ` necessario puntualizzare che i run controllati e i dati raccolti non e 8
  9. 9. possono essere considerati per la definizione di una precisa tabella delle performance(dalla quale ` possibile stabilire, con certezza, quale sia l’applicazione migliore per una ecerta caratteristica in esame) a causa dei seguenti motivi: 1. Numero di run: Il numero di esecuzioni controllate non ` tale da stabilire una e corretta descrizione generale del comportamento delle due applicazioni. 2. Contesto software: Il contesto software in cui le applicazioni sono state eseguite non rispecchiava un classico scenario di esame. Ovvero la presenza di processi in background di sistema e di altre applicazioni ha inquinato l’ambiente di lavoro e ha, pertanto, reso le grandezze in esame, dipendenti da parametri rumorosi. 3. Contesto hardware: La macchina su cui sono state condotte le esecuzioni non rispecchiava 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 StrumentazioneAl fine di ottenere dati precisi per analisi in profondit` delle performance delle due aapplicazioni, coerentemente al contesto software di base (sistema operativo), ` stata eutilizzata un’applicazione Microsoft per il profiling di una sessione di lavoro. MicrosoftWindows Performance Analysis (WPA) ` stata scelta come tool principale per l’analisi edelle prestazioni sopratutto a fronte della sua perfetta integrazione coi sistemi Windows(il set di tool interagisce perfettamente con le funzionalit` di sistema, rendendo la suite aun componente nativo di Windows). Il risultato ` la possibilit` di ottenere informazioni e adavvero precise circa l’esecuzione di una qualsiasi applicazione.5.2.1 Algoritmo di generazione delle reti complessePer generare le reti complesse utilizzate nelle sessioni di run, ` stato utilizzato l’algoritmo edi Watts e Strogatz.5.3 Sessione di esecuzione a reti dimensione-variataQuesta sessione di esecuzioni ha visto i due programmi competere su reti a dimensionevariabile. Lo scopo ` quello di verificare l’andamento delle prestazioni al crescere del enumero dei nodi, mantenendo costante, nei limiti del possibile, la struttura di vicinatodella rete.5.3.1 Reti caricateLe 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 complessit`. a5.3.2 Risposta dei tempi di esecuzioneI tempi di esecuzione hanno avuto due differenti andamenti: 9
  10. 10. • PathFinder: L’applicazione ha mostrato un andamento crescente dei tempi. La dilatazione temporale evidenziata da PathFinder mette in mostra una dipendenza coerente tra numero di nodi e tempi. La crescita diviene considerevole a partire dai 1000 nodi in su, dove il salto in ordine di grandezza determina una netta spaccatura nella complessit` dele operazioni da eseguire. Tuttavia, l’applicazione a risponde 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 al crescere del numero dei nodi. La rete ad 8000 nodi non viene caricata al completo e l’applicazione sperimanta un crash non occasionale (run multipli sulla configu- razione a 8000 nodi mostrano che Traffic interrompe l’esecuzione a seguito dello stesso crash).5.3.3 Risposta dei tempi di IOI 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 dei tempi. 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 di ricerca del percorso ottimo (tempo di pura computazione). I dati mostrano, infatti, un andamento crescente delle attivit` di IO che, per`, riscontrano significativi in- a o nalzamenti solamente nella transizione da certi numeri di nodi. Dunque la crescita ` si presente, ma non costante, tuttavia regolare. Da notare, il raggiungimento dei e 7000 e 8000 nodi dove un brusco innalzamento viene rilevato. Si tratta, ed ` bene e sottolinearlo, di un pattern comunque regolare, infatti la curva dei tempi mostra variazioni d’ordine e tratti di non variazione d’ordine susseguirsi regolarmente. • Traffic: L’applicazione mostra un pattern fortemente irregolare nei tempi di IO al crescere del numero dei nodi. La crescita si mantiene piuttosto regolare fino al raggiungimento dei 1000 nodi, oltre i quali vengono sperimentati sbalzi acuti dei tempi. Tale comportamento evidenzia molte irregolarit` nell’esecuzione. a5.3.4 Rapporto dei tempi di computazione ed IOI 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 attivit` con il filesystem dandoci ala possibilit` di valutare quante parte dell’esecuzione viene materialmente spesa nelle aoperazioni 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 un complementare 75% circa viene sempre dedicato alla gestione delle operazioni su file. Tale risultato mette in luce la propriet` pi` importante di PathFinder: la a u lentezza crescente, al crescere della rete, ` dovuta nettamente ai tempi necessari e affinch` la rete possa essere caricata e tradotta. e 10
  11. 11. • Traffic: L’applicazione mette in luce una percentuale estremamente a favore dei tempi di computazione, segno del fatto che i tempi di accesso e modifica dei file di configurazione, constano di un parte davvero minima nell’esecuzione complessiva.5.4 Sessione di esecuzione a reti struttura-variataQuesta 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 il processo itera i vari nodi generando nuove connessioni con una data probabilit`. a • Barabasi: La rete viene generata a partire da una rete triangolare. Alla rete vengono dunque aggiunti i vari nodi con un dato numero di connessioni. La prob- abilit` che tali connessioni vengano agganciate ai vari nodi ` proporzionale al loro a e vicinato. • Bernoulli: La rete viene generata a partire da una rete di base con tutti i nodi. Le connessioni vengono create secondo lo schema stocastico a tentativi di Bernoulli.Lo scopo ` quello di verificare l’andamento delle prestazioni al crescere del connession- eismo per le varie tipologie di reti.5.4.1 Reti caricateLe 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 complessit`. a5.4.2 Risultati in generaleI run hanno messo in luce un comportamento molto statico dei tempi di esecuzioneda parte di Traffic, mentre una maggiore sensibilit` viene riscontrata da PathFinder. aRiguardo le percentuali di effettiva computazione ed IO, troviamo sempre una grandedisparit` tra le due applicazioni, dove Traffic utilizza sempre non meno del 25% del atempo totale, in operazioni di IO.5.5 Sessione di esecuzione a reti heuristic-awareQuesta 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 generaleUn 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 pi` lento, considerando il upuro tempo di computazione (eliminando l’IO complessivo), PathFinder riesce a trovareil cammino minimo in pochi millisecondi. 11

×