Skip to content

Che cos'è la frammentazione della memoria?

I nostri migliori ricercatori hanno esaurito le scorte di caffè, cercando giorno e notte la soluzione, finché Alejandra non ha trovato la soluzione su Bitbucket, quindi oggi la condividiamo con te.

Soluzione:

Immaginate di avere una "grande" (32 byte) porzione di memoria libera:

----------------------------------
|                                |
----------------------------------

Ora, allocatene una parte (5 allocazioni):

----------------------------------
|aaaabbccccccddeeee              |
----------------------------------

Ora, liberate le prime quattro allocazioni ma non la quinta:

----------------------------------
|              eeee              |
----------------------------------

Ora, provate ad allocare 16 byte. Oops, non posso, anche se c'è quasi il doppio di spazio libero.

Sui sistemi con memoria virtuale, la frammentazione è meno problematica di quanto si possa pensare, perché le allocazioni di grandi dimensioni devono essere contigue solo in virtuale spazio degli indirizzi, non in fisico spazio degli indirizzi. Quindi, nel mio esempio, se avessi una memoria virtuale con una dimensione di pagina di 2 byte, potrei allocare i miei 16 byte senza problemi. La memoria fisica avrebbe questo aspetto:

----------------------------------
|ffffffffffffffeeeeff            |
----------------------------------

mentre la memoria virtuale (essendo molto più grande) potrebbe avere questo aspetto:

------------------------------------------------------...
|              eeeeffffffffffffffff                   
------------------------------------------------------...

Il classico sintomo della frammentazione della memoria è che si cerca di allocare un blocco di grandi dimensioni e non ci si riesce, anche se sembra che ci sia abbastanza memoria libera. Un'altra possibile conseguenza è l'incapacità del processo di rilasciare la memoria al sistema operativo (perché ognuno dei blocchi di grandi dimensioni che ha allocato dal sistema operativo, per esempio, non è in grado di allocare memoria. malloc ecc. da suddividere, ha ancora qualcosa al suo interno, anche se la maggior parte di ogni blocco è ora inutilizzata).

Le tattiche per prevenire la frammentazione della memoria in C++ funzionano allocando oggetti da aree diverse in base alla loro dimensione e/o alla loro durata prevista. Quindi, se si intende creare molti oggetti e distruggerli tutti insieme in seguito, li si alloca da un pool di memoria. Qualsiasi altra allocazione effettuata tra i due oggetti non proverrà dal pool e quindi non si troverà tra di essi in memoria, per cui la memoria non sarà frammentata. In alternativa, se si intende allocare molti oggetti della stessa dimensione, li si alloca dallo stesso pool. In questo modo un tratto di spazio libero nel pool non potrà mai essere più piccolo della dimensione che si sta cercando di allocare da quel pool.

In genere non ci si deve preoccupare più di tanto, a meno che il programma non sia di lunga durata e faccia molte allocazioni e liberazioni. È quando si hanno miscele di oggetti a vita breve e a vita lunga che si rischia di più, ma anche in questo caso malloc farà del suo meglio per aiutare. Fondamentalmente, ignoratelo finché il vostro programma non presenta errori di allocazione o non causa inaspettatamente un esaurimento della memoria del sistema (prendetelo nei test, per preferenza!).

Le librerie standard non sono peggiori di qualsiasi altra cosa che alloca memoria, e i contenitori standard hanno tutti un'opzione Alloc che si può usare per mettere a punto la strategia di allocazione, se assolutamente necessario.

Che cos'è la frammentazione della memoria?

La frammentazione della memoria si ha quando la maggior parte della memoria è allocata in un gran numero di blocchi o chunk non contigui, lasciando una buona percentuale della memoria totale non allocata, ma inutilizzabile per la maggior parte degli scenari tipici. Ciò comporta eccezioni di esaurimento della memoria o errori di allocazione (ad esempio, malloc restituisce null).

Il modo più semplice di pensare a questo problema è immaginare di avere una grande parete vuota in cui si devono mettere delle foto di varie dimensioni su di essa. Ogni quadro occupa una certa dimensione e ovviamente non è possibile dividerlo in pezzi più piccoli per farlo entrare. È necessario un posto vuoto sulla parete, della dimensione del quadro, altrimenti non è possibile appenderlo. Se iniziate ad appendere i quadri alla parete e non fate attenzione a come li disponete, vi ritroverete presto con una parete parzialmente ricoperta di quadri e, anche se ci sono dei posti vuoti, la maggior parte dei nuovi quadri non ci starà perché sono più grandi dei posti disponibili. È ancora possibile appendere quadri molto piccoli, ma la maggior parte non ci entrerà. Quindi dovrete riorganizzare (compattare) quelli già presenti sulla parete per fare spazio ad altri.

Ora, immaginate che il muro sia la vostra memoria (heap) e che i quadri siano oggetti... Questa è la frammentazione della memoria...

Come si fa a capire se la frammentazione della memoria è un problema per la propria applicazione? Che tipo di programma è più probabile che ne soffra?

Un segno rivelatore della frammentazione della memoria è il verificarsi di molti errori di allocazione, soprattutto quando la percentuale di memoria utilizzata è elevata, ma non è stata ancora utilizzata tutta la memoria, quindi tecnicamente si dovrebbe avere molto spazio per gli oggetti che si sta cercando di allocare.

Quando la memoria è molto frammentata, le allocazioni di memoria richiederanno probabilmente più tempo, perché l'allocatore di memoria deve fare più lavoro per trovare uno spazio adatto al nuovo oggetto. Se a sua volta si hanno molte allocazioni di memoria (cosa probabile, visto che si è arrivati alla frammentazione della memoria), il tempo di allocazione può causare ritardi evidenti.

Quali sono i modi più comuni per gestire la frammentazione della memoria?

Utilizzare un buon algoritmo per l'allocazione della memoria. Invece di allocare la memoria per molti piccoli oggetti, preallocate la memoria per un array contiguo di oggetti più piccoli. A volte, un po' di spreco nell'allocazione della memoria può essere utile per le prestazioni e può risparmiare il problema della frammentazione della memoria.

La frammentazione della memoria è lo stesso concetto della frammentazione del disco: si riferisce allo spazio sprecato perché le aree in uso non sono abbastanza vicine tra loro.

Supponiamo, per un semplice esempio giocattolo, di avere dieci byte di memoria:

 |   |   |   |   |   |   |   |   |   |   |
   0   1   2   3   4   5   6   7   8   9

Ora allochiamo tre blocchi da tre byte, denominati A, B e C:

 | A | A | A | B | B | B | C | C | C |   |
   0   1   2   3   4   5   6   7   8   9

Ora deallocare il blocco B:

 | A | A | A |   |   |   | C | C | C |   |
   0   1   2   3   4   5   6   7   8   9

Ora cosa succede se proviamo ad allocare un blocco a quattro byte D? Abbiamo quattro byte di memoria libera, ma non abbiamo quattro byte di memoria libera. contigui di memoria libera, quindi non possiamo allocare D! Questo è un uso inefficiente della memoria, perché avremmo dovuto essere in grado di memorizzare D, ma non ci siamo riusciti. E non possiamo spostare C per fare spazio, perché molto probabilmente alcune variabili del nostro programma puntano a C e non possiamo trovare e cambiare automaticamente tutti questi valori.

Come si fa a sapere che si tratta di un problema? Il segno più evidente è che la dimensione della memoria virtuale del programma è notevolmente più grande della quantità di memoria effettivamente utilizzata. In un esempio reale, si avrebbero molti più di dieci byte di memoria, quindi D verrebbe allocato a partire dal byte 9 e i byte 3-5 rimarrebbero inutilizzati, a meno che non venga allocato qualcosa di tre byte o più piccolo.

In questo esempio, 3 byte non sono un granché da sprecare, ma si consideri un caso più patologico in cui due allocazioni di un paio di byte si trovano, per esempio, a dieci megabyte di distanza in memoria e si ha bisogno di allocare un blocco di dimensione 10 megabyte + 1 byte. Per farlo, bisogna chiedere al sistema operativo oltre dieci megabyte di memoria virtuale in più, anche se manca solo un byte per avere già spazio sufficiente.

Come si può evitare? I casi peggiori tendono a verificarsi quando si creano e distruggono frequentemente piccoli oggetti, poiché ciò tende a produrre un effetto "formaggio svizzero" con molti piccoli oggetti separati da molti piccoli buchi, rendendo impossibile allocare oggetti più grandi in quei buchi. Quando si sa di doverlo fare, una strategia efficace è quella di pre-allocare un grande blocco di memoria come pool per i piccoli oggetti e poi gestire manualmente la creazione dei piccoli oggetti all'interno di quel blocco, piuttosto che lasciare che sia l'allocatore predefinito a gestirlo.

In generale, meno allocazioni si fanno, meno è probabile che la memoria venga frammentata. Tuttavia, STL gestisce questo problema in modo piuttosto efficace. Se si ha una stringa che sta utilizzando l'intera allocazione corrente e si aggiunge un carattere, la stringa non viene semplicemente riallocata alla sua lunghezza corrente più uno, ma viene raddoppia la sua lunghezza. Questa è una variante della strategia "pool per allocazioni piccole e frequenti". La stringa sta prendendo una grande porzione di memoria, in modo da poter gestire in modo efficiente piccoli aumenti ripetuti di dimensione senza dover effettuare ripetute riallocazioni di piccole dimensioni. Tutti i contenitori STL fanno questo genere di cose, quindi in genere non ci si deve preoccupare troppo della frammentazione causata dalla riallocazione automatica dei contenitori STL.

Anche se ovviamente i contenitori STL non mettono in comune la memoria tra tra loro, quindi se si vogliono creare molti piccoli contenitori (piuttosto che pochi contenitori che vengono ridimensionati frequentemente) ci si deve preoccupare di prevenire la frammentazione nello stesso modo in cui si farebbe per qualsiasi piccolo oggetto creato frequentemente, STL o meno.

valutazioni e commenti

Se il nostro post ti è stato utile, sarebbe molto utile se lo condividessi con altri anziani e ci aiutassi ad ampliare i nostri contenuti.



Utilizzate il nostro motore di ricerca

Ricerca
Generic filters

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.