Lez. 71 Universita' di Ferrara Facolta' di Scienze Matematiche, Fisiche e Naturali Laurea Specialistica in Informatica Algoritmi Avanzati Gestione Dinamica della Memoria: boundary-tag method Vedi: E. Horowitz, S. Sahni, Fundamentals of data structures, Computer Science Press F. de Luigi, C. Salati, Fondamenti di Informatica - Esercitazioni, Gabriele Corbo Editore Copyright © by Claudio Salati.
2 Gestione dinamica della memoria La gestione della memoria dinamica in linguaggi come Pascal, C e C++ non e' soggetta ad alcuna particolare disciplina (come accade invece alla memoria automatica che e' allocata e disallocata con disciplina LIFO, ed e' quindi facilmente implementata utilizzando uno stack) Inoltre, i blocchi di memoria che sono allocati e liberati sono di dimensione variabile. Problema (si pone anche per i Sistemi Operativi): tenere traccia dei blocchi di memoria libera e dei blocchi occupati realizzare due operazioni: la prima per consentire di allocare un blocco di memoria della dimensione desiderata ( malloc() in C) la seconda per consentire di liberare un blocco di memoria ( free() in C)
3 Gestione dinamica della memoria: lista dei blocchi liberi Per tenere traccia dei blocchi di memoria libera si potrebbe pensare di utilizzare una lista ogni blocco libero riferisce il prossimo blocco libero della lista. La responsabilita' di tenere traccia di quali siano i blocchi occupati e' lasciata invece al programma utente se un blocco e' occupato, il programma utente lo sta utilizzando! Un blocco libero viene frazionato a fronte di una operazione di allocazione che non lo occupi completamente. Ogni blocco, libero o occupato, deve mantenere registrata la propria dimensione ai blocchi liberi serve per gestire la operazione di allocazione ai blocchi occupati serve perche' la funzione di free() non indica la dimensione del blocco che si libera (quindi l'informazione deve essere presente nel blocco stesso!) All'inizio tutta la memoria (che e' libera) e' costituita da un unico blocco.
4 Gestione dinamica della memoria: lista dei blocchi liberi testata size testata size testata size testata size occupato size libero
5 Gestione dinamica della memoria: lista dei blocchi liberi Dopo un po' la memoria libera risulta frammentata! Anche blocchi liberi adiacenti non sono ricompattati in un unico blocco Puo' capitare che una richiesta di allocazione di un blocco di dimensione S venga rifiutata anche se complessivamente la memoria libera e' piu' di S, e anche se esiste una quantita' di memoria libera adiacente (ma frammentata in piu' blocchi) maggiore di S. la memoria libera non e' gestibile secondo una semplice lista di blocchi liberi perche' un frazionamento irreversibile porterebbe alla frammentazione (l'entropia aumenta continuamente!)
6 Politiche di allocazione: Best-Fit vs. First-Fit L'operazione di allocazione non acquisisce necessariamente un blocco intero: puo' ottenere solo una frazione di blocco. E' possibile limitare la frammentazione tramite una opportuna politica di allocazione/frazionamento? Si possono ad esempio immaginare 2 diverse politiche di allocazione/frazionamento: best-fit first-fit
7 Politiche di allocazione: Best-Fit vs. First-Fit Politica di allocazione Best-Fit: tra i blocchi liberi di dimensione maggiore o uguale a quella desiderata cerchiamo quello di dimensione minima. Per trovarlo occorre (durante l'allocazione): scandire tutta la lista dei liberi, se questa non e' ordinata per dimensione (crescente) dei blocchi scandire "in media" meta' della lista dei liberi, se questa e' ordinata per dimensione (crescente) dei blocchi In ogni caso l'operazione di allocazione ha complessita' O(n), essendo n la lunghezza della lista dei blocchi liberi L'operazione di rilascio ha complessita' O(1) se la lista dei liberi non e' mantenuta ordinata ha complessita' O(n) se la lista dei liberi e' mantenuta ordinata (bisogna scandire "in media" meta' della lista)
8 Politiche di allocazione: Best-Fit vs. First-Fit Politica di allocazione First-Fit: ci si accontenta di qualunque blocco libero di dimensione maggiore o uguale a quella desiderata. Per trovarlo occorre (durante l'allocazione): scandire la lista dei liberi fino a che si trova il primo blocco di dimensione sufficiente in questo caso non ha molto senso volere mantenere la lista ordinata per dimensione dei blocchi In ogni caso l'operazione di allocazione ha complessita' O(n), ma anche se i blocchi non sono ordinati non e' mediamente necessario scandire tutta la lista L'operazione di rilascio ha complessita' O(1) perche' la lista dei liberi non e' mantenuta ordinata
9 Politiche di allocazione: Best-Fit vs. First-Fit L'ordine di complessita' delle due politiche e' lo stesso. Sperimentalmente pero' risulta migliore First-Fit. Questo risulta anche abbastanza intuitivo dall'analisi dei casi che abbiamo fatto. Nel seguito faremo sempre riferimento alla politica First-Fit e quindi non manterremo alcun ordine (per dimensione) nella lista dei blocchi liberi Per una analisi (anche sperimentale) molto dettagliata del problema vedi in particolare: D.E. Knuth, The art of computer programming, Vol. 1, Fundamental Algorithms, Addison-Wesley, sez. 2.5, Dynamic storage allocation che ha anche introdotto l'algoritmo presentato in questa lezione
10 Politiche di allocazione: Ottimizzazioni euristiche - 1 E' conveniente effettuare il frazionamento di un blocco occupandone il fondo: in questo modo la struttura della lista dei blocchi liberi non viene modificata, eccetto nel caso in cui il blocco selezionato abbia esattamente la dimensione richiesta in questo caso esso non viene frazionato ma diventa di per se' un blocco occupato. Ovviamente, se c'e' frazionamento, la dimensione del blocco libero deve essere aggiornata ma non occorre ri-ordinare la lista dei liberi, visto che questa non e' ordinata per dimensione dei blocchi.
11 Politiche di allocazione: Ottimizzazioni euristiche - 2 Il frazionamento puo' comunque provocare la generazione di blocchi troppo piccoli per essere mai utilizzati: se li teniamo nella lista dei liberi perderemo comunque tempo per scandirli (inutilmente); ma allora, perche' crearli? Definiamo un quanto di frazionamento minimo: se il frazionamento portasse a creare un blocco di dimensione minore del quanto minimo, per non frazionare daremo al cliente un blocco piu' grande di quello che ha richiesto. il cliente non sa quanta memoria riceve esattamente e' almeno tanta quanta ne ha richiesta Poiche' il cliente non sa quanto e' grande esattamente un blocco occupato, deve essere il blocco occupato stesso a tenere registrata la propria dimensione.
12 Politiche di allocazione: Ottimizzazioni euristiche - 3 Se durante l'operazione di allocazione cominciamo la ricerca di un blocco adatto sempre nel medesimo punto della lista dei blocchi liberi, cio' portera' ad ammassare in quel punto blocchi di piccola dimensione (a causa dei frazionamenti). Anche se non violano la regola del quanto di frazionamento minimo questi blocchi spesso risulteranno troppo piccoli. Conviene quindi distribuire i blocchi piccoli su tutta la lista dei blocchi liberi: cio' puo' essere fatto iniziando la ricerca, ad ogni richiesta di allocazione, da un punto diverso della lista; basta utilizzare per la lista dei liberi una lista circolare, e cominciare la ricerca dal blocco successivo a quello che si e' scelto durante l'operazione di allocazione precedente. Questi miglioramenti non cambiano l'ordine di complessita' dell'algoritmo, ma ne migliorano i coefficienti moltiplicativi e i termini di ordine inferiore
13 Rilascio con compattamento - 1 Il problema vero e' di eliminare per quanto possibile la frammentazione della memoria. Non ci possiamo accontentare di inserire un blocco che vogliamo liberare nella lista dei liberi: in questo modo i frazionamenti sarebbero irreversibili. Quando liberiamo un blocco di memoria lo vogliamo ricompattare con eventuali blocchi adiacenti gia' liberi ricreando in questo modo blocchi contigui di dimensione massima possibile.
14 Rilascio con compattamento - 1 Ci sono due punti da prendere in considerazione: 1.Come sapere se i blocchi fisicamente adiacenti (precedente e/o successivo) a quello che vogliamo liberare sono liberi o occupati? 2.Una volta stabilito che uno o entrambi i blocchi adiacenti sono liberi, come effettuare il compattamento? N.B.: in generale sara' necessario alterare la struttura della lista. Se per fare tutto questo (o l'una o l'altra cosa) occorresse scandire la lista dei blocchi liberi il costo di un rilascio sarebbe O(n)!
15 Identificazione di blocchi liberi adiacenti La prima soluzione consiste nello scandire la lista dei blocchi liberi per verificare se ce ne sono di adiacenti in questo senso si potrebbe ipotizzare che fosse conveniente mantenere la lista dei blocchi liberi ordinata (non per dimensione ma) per indirizzo di inizio del blocco in questo modo non si sarebbe costretti a scandirla tutta, ma solo fino a che si incontra un blocco con indirizzo di inizio maggiore della fine dell blocco in via di liberazione + 1 Ma anche in questo modo l'identificazione di eventuali blocchi liberi adiacenti avrebbe complessita' O(n)
16 Compattamento e ristrutturazione della lista dei blocchi liberi Una volta individuati gli eventuali blocchi liberi adiacenti e' veramente necessario rimuoverli dalla lista dei liberi per inserirci poi il nuovo blocco compattato o si puo' fare di meglio? La ristrutturazione della lista risulta comunque O(1) una volta che essa sia doppiamente linkata! Notare che una eventuale scansione della lista dei blocchi liberi per identificare eventuali blocchi liberi adiacenti ci faciliterebbe anche un inserimento ordinato (per indirizzo) del nuovo blocco libero nella lista!
17 Rilascio con compattamento - 2 Ogni blocco e' comunque contiguo ad almeno un altro blocco (perche' non sempre a 2?), libero o occupato che sia. Allora strutturo ogni blocco, libero od occupato che sia, introducendo in esso delle informazioni aggiuntive, che nel caso di un blocco occupato sono comunque nascoste al programma cliente Ad ogni blocco, libero od occupato, diamo la seguente struttura: Tag = occupato vs. libero Dimensione del blocco … Tag = occupato vs. libero Indirizzo ritornato al cliente al momento dell'allocazione (per un blocco occupato)
18 Rilascio con compattamento - 3 Con questa strutturazione, dato un blocco, guardando i campi tag dei blocchi fisicamente adiacenti, possiamo dire (in tempo O(1)) se questi sono liberi o occupati Questa strutturazione interna e' sufficiente per i blocchi occupati, Infatti e' responsabilita' del programma cliente mantenere l'identita' dei blocchi occupati La struttura interna di un blocco libero deve invece essere ancora arricchita: perche' devo mantenere il blocco libero nella lista dei blocchi liberi; perche' la lista dei blocchi liberi deve essere doppiamente linkata (per consentire l'eliminazione di un blocco dalla lista in tempo O(1) conoscendone l'indirizzo); perche' se il blocco precedente a quello che voglio liberare e' gia' libero devo potere recuperare il suo indirizzo di inizio per poter operare il compattamento.
19 Rilascio con compattamento - 3' Nota che l'utilizzo dei campi tag di libero/occupato ha eliminato ogni necessita' di mantenere ordinati i blocchi nella lista dei liberi. Un ordinamento per dimensione avrebbe potuto servire per implementare la politica best-fit, alla quale abbiamo pero' rinunciato. Un ordinamento per indirizzo di inizio avrebbe potuto essere utile nella ricerca di eventuali blocchi liberi adiacenti, ma non se per questo scopo utilizziamo i campi tag. Quindi, in assenza di blocchi liberi fisicamente adiacenti a quello che stiamo liberando, basta semplicemente inserire quest'ultimo in qualunque posizione della lista dei liberi, e.g. in coda. In realta' bisogna anche completarne la strutturazione interna aggiungendo tutte le informazioni necessarie ad un blocco libero.
20 Rilascio con compattamento - 4 Struttura completa di un blocco libero: … rLink lLink upLink Tag = libero Dimensione del blocco Tag = libero
21 Rilascio con compattamento - 5 Al momento del rilascio si presentano 4 casi, a seconda che i due blocchi fisicamente adiacenti, il precedente ed il successivo, siano liberi od occupati. La lista dei blocchi liberi deve essere doppiamente connessa per poterla ristrutturare in caso di compattamento (casi 2 e 4). 1.Se sono occupati sia il blocco fisicamente precedente che quello fisicamente successivo bisogna inserire il blocco che andiamo a liberare nella lista dei blocchi liberi 2.Se e' libero solo il blocco fisicamente successivo a quello che vogliamo liberare (e quello precedente e' occupato) bisogna estrarre il blocco successivo dalla lista dei liberi e inserirci il blocco che liberiamo, estendendolo per inglobare anche il blocco libero che abbiamo eliminato (quello successivo). Continua alla pagina successiva
22 Rilascio con compattamento Se e' libero solo in blocco fisicamente precedente a quello che vogliamo liberare (e quello successivo e' occupato) basta estendenderlo per inglobare anche il blocco che stiamo liberando 4.Se sono liberi sia il blocco fisicamente precedente che quello fisicamente successivo a quello che vogliamo liberare occorre estrarre il blocco successivo dalla lista dei liberi e estendendere il blocco precedente per inglobare sia il blocco che stiamo liberando che quello successivo Ovviamente quando inseriamo un nuovo blocco libero lo dobbiamo strutturare internamente in modo appropriato.
23 Campo Dimensione Il campo dimensione del blocco deve essere comunque presente, sia nei blocchi liberi che in quelli occupati Di un blocco libero occorre conoscerne la dimensione: durante l'operazione di allocazione, per vedere se e' sufficientemente grande ed eventualmente gestirne il frazionamento durante l'operazione di liberazione, per gestire l'eventuale compattamento In un blocco occupato deve esserci registrata la sua (vera) dimensione perche' il cliente non la conosce: per evitare creare blocchi liberi di dimensione inferiore al quanto minimo di frazionamento potrebbe essergli stata data piu' memoria di quanta ne avesse richiesta Un quanto di frazionamento minimo deve comunque essere considerato, perche' un blocco libero deve essere grande almeno abbastanza da contenere i sei campi strutturali necessari a gestirlo.
24 Eliminazione dei casi particolari Nella coda dei liberi inseriamo un nodo di testata di dimensione 0 (quindi non allocabile) per evitare di gestire il caso di coda dei liberi vuota in inserzione e cancellazione Il nodo di testata non deve mai potere essere adiacente ad un blocco libero (liberato), per non essere compattato con questo Ai margini fisici dell'area di memoria gestita dinamicamente inseriamo due pseudo campi tag con valore occupato. Questi campi evitano di dovere considerare come casi particolari il rilascio dei blocchi ai bordi dell'area dinamica In questo modo ogni blocco che liberiamo ha sempre esattamente 2 blocchi adiacenti (liberi o occupati, veri o pseudo), uno prima e uno dopo
25 Struttura iniziale della memoria dinamica Pseudo-tag di bordo fisico della memoria dinamica memoria dinamica Blocco libero dummy di dimensione 0 Tag = occupato Tag = libero rLink lLink upLink Dimensione del blocco … Tag = libero Tag = occupato rLink lLink upLink Tag = libero Dimensione del blocco = 0 Tag = libero Testata della coda dei blocchi liberi
26 Complessita' delle operazioni L'operazione di rilascio di un blocco, compreso l'eventuale compattamento con i blocchi liberi adiacenti (se ci sono), e' di complessita' O(1) (costante). Tramite boundary-tag e campo upLink posso verificare se i blocchi adiacenti sono liberi o occupati e recuperarne l'identita', cioe' l'indirizzo di inizio (in tempo O(1)) l'identita' del blocco fisicamente successivo e' calcolata come indirizzo successivo all'indirizzo dell'ultima cella del blocco l'identita' del blocco fisicamente precedente e' calcolata tramite l'uplink di questo blocco dopo che l'indirizzo della sua ultima cella e' stato calcolato come indirizzo precedente quello della prima cella del blocco Utilizzando una lista doppiamente linkata e' possibile ristrutturare in tempo O(1) la lista dei blocchi liberi in caso di compattamento Utilizzando una struttura dati piu' semplice (lista semplicemente connessa, non utilizzo di boundary tag) sarebbe stata di complessita' O(n): il miglioramento e' stato quindi di un ordine.
27 Complessita' delle operazioni L'operazione di allocazione di un blocco continua ad avere complessita' O(n), ma la complessita' attesa non coincide con il caso peggiore (un qualunque blocco sufficientemente grande va bene!) Per ottenere questi risultati si e' dovuto assumere una politica di allocazione First-Fit questo ci evita di dovere mantenere qualunque ordinamento nella lista dei blocchi liberi L'introduzione di un blocco libero di testata e degli pseudo-tag di bordo fisico dell'area della memoria dinamica migliorano i coefficienti moltiplicativi e additivi della complessita' a parita' di ordine semplificano la scrittura del codice