Gestione intelligente della memoria con operazioni a tempo costante?


18

Consideriamo un segmento di memoria (le cui dimensioni possono aumentare o ridursi, come un file, quando necessario) su cui è possibile eseguire due operazioni di allocazione di memoria di base che coinvolgono blocchi di dimensioni fisse:

  • assegnazione di un blocco
  • liberando un blocco precedentemente assegnato che non viene più utilizzato.

Inoltre, come requisito, il sistema di gestione della memoria non è autorizzato a spostarsi tra i blocchi attualmente allocati: il loro indice / indirizzo deve rimanere invariato.

L'algoritmo di gestione della memoria più ingenuo incrementerebbe un contatore globale (con valore iniziale 0) e userebbe il suo nuovo valore come indirizzo per l'allocazione successiva. Tuttavia, ciò non consentirà mai di accorciare il segmento quando rimangono solo pochi blocchi allocati.

Approccio migliore: mantenere il contatore, ma mantenere un elenco di blocchi deallocati (che possono essere eseguiti in tempo costante) e utilizzarlo come fonte per nuove allocazioni purché non sia vuoto.

Quale prossimo? C'è qualcosa di intelligente che può essere fatto, sempre con vincoli di allocazione e deallocazione del tempo costanti, che manterrebbe il segmento di memoria il più corto possibile?

(Un obiettivo potrebbe essere quello di tracciare il blocco attualmente non allocato con l'indirizzo più piccolo, ma non sembra fattibile in tempo costante ...)


il controllo di un elenco non sarebbe più di tempo costante, poiché l'elenco potrebbe crescere o ridursi a causa di alcune allocazioni / disallocazioni effettuate in precedenza?
Sim

@Sim, stavo assumendo che sia un elenco collegato e con esso le operazioni sarebbero O(N) , perché lavori sempre solo con la testa.
svick

Penso che il tuo "approccio migliore" userà già una quantità ottimale di memoria, cioè non assegnerà mai memoria aggiuntiva se c'è un blocco libero. Come immagini che l'approccio "intelligente" migliorerebbe al riguardo? Vuoi dire che dovrebbe essere allocato vicino all'inizio in modo che ci sia una migliore possibilità che tu possa ridurre il segmento dopo le deallocazioni?
svick

@Sim: Mi dispiace, forse avrei dovuto usare il termine stack (ma ho pensato che potesse essere fonte di confusione), 'deallocate' è push e 'allocate' è pop, o nel caso in cui fallisca basta ricorrere all'incremento del contatore. Entrambi sono tempi costanti.
Stéphane Gimenez,

Hai vincoli in tempo reale o stai bene con il tempo costante ammortizzato? Le risposte sono probabilmente diverse.
Gilles 'SO- smetti di essere malvagio' il

Risposte:


11

Con blocchi di dimensioni fisse, quello che hai descritto è un elenco gratuito . Questa è una tecnica molto comune, con la seguente svolta: l'elenco dei blocchi liberi è memorizzato nei blocchi liberi stessi. Nel codice C, sarebbe simile al seguente:

static void *alloc_ptr = START_OF_BIG_SEGMENT;
static void *free_list_head = NULL;

static void *
allocate(void)
{
    void *x;

    if (free_list_head == NULL) {
        x = alloc_ptr;
        alloc_ptr = (char *)alloc_ptr + SIZE_OF_BLOCK;
    } else {
        x = free_list_head;
        free_list_head = *(void **)free_list_head;
    }
    return x;
}

static void
release(void *x)
{
    *(void **)x = free_list_head;
    free_list_head = x;
}

Funziona bene fintanto che tutti i blocchi allocati hanno le stesse dimensioni e tale dimensione è un multiplo della dimensione di un puntatore, in modo da mantenere l'allineamento. Allocazione e deallocazione sono a tempo costante (ovvero, a tempo costante come gli accessi alla memoria e le aggiunte elementari) in un computer moderno, un accesso alla memoria può comportare errori di cache e persino memoria virtuale, quindi accessi al disco, quindi il "tempo costante" può essere abbastanza grande). Non c'è sovraccarico di memoria (nessun puntatore per blocco extra o cose del genere; i blocchi allocati sono contigui). Inoltre, il puntatore di allocazione raggiunge un dato punto solo se, contemporaneamente, dovevano essere allocati molti blocchi: poiché l'allocazione preferisce usare la lista libera, il puntatore di allocazione viene aumentato solo se lo spazio sotto il puntatore corrente è pieno. In tal senso, tecnica.

Decrescenteil puntatore di allocazione dopo un rilascio può essere più complesso, poiché i blocchi liberi possono essere identificati in modo affidabile solo seguendo l'elenco libero, che li attraversa in un ordine imprevedibile. Se per te è importante ridurre le dimensioni del segmento grande, se possibile, potresti voler usare una tecnica alternativa, con più overhead: tra due blocchi allocati, metti un "buco". I fori sono collegati tra loro con un elenco doppiamente collegato, in ordine di memoria. È necessario un formato dati per un foro in modo tale da poter individuare l'indirizzo iniziale del foro sapendo dove finisce e anche la dimensione del foro se si sa dove inizia il foro in memoria. Quindi, quando si rilascia un blocco, si crea un foro che si fonde con i fori successivi e precedenti, ricostruendo (ancora a tempo costante) l'elenco ordinato di tutti i fori. Il sovraccarico è quindi di circa due parole delle dimensioni di un puntatore per blocco assegnato; ma, a quel prezzo, è possibile rilevare in modo affidabile il verificarsi di un "foro finale", ovvero un'occasione per ridurre le dimensioni del segmento di grandi dimensioni.

Esistono molte varianti possibili. Un buon documento introduttivo è Dynamic Storage Allocation: A Survey and Critical Review di Wilson et al.


4
Come trovare i fori più vicini a un sito di deallocazione in tempo costante?
Raffaello

1
Nel secondo metodo che descrivo, un foro è un'intestazione (una coppia di puntatori, per l'elenco dei fori) insieme allo spazio per zero, uno o più blocchi di dati. Tra due blocchi assegnati, c'è sempre un foro, anche se si tratta di un micro-foro costituito solo da un'intestazione del foro. Quindi individuare i fori più vicini è facile: sono subito prima e subito dopo lo slot. Naturalmente, i micro-fori non fanno parte dell'elenco gratuito (l'elenco dei fori idonei per l'allocazione). Un altro modo per visualizzarlo è che aggiungi un'intestazione a ogni blocco e ogni foro (non micro) (allocazione sotto Ms-Dos a 16 bit funzionava così).
Thomas Pornin

4

Questa risposta riguarda le tecniche di gestione della memoria generiche. Ho perso la domanda sul caso in cui tutti i blocchi hanno le stesse dimensioni (e sono allineati).


Le strategie di base che dovresti conoscere sono il primo, il prossimo, il migliore e il sistema amico . Ho scritto una breve sintesi una volta per un corso che ho insegnato, spero sia leggibile. Indico lì un sondaggio abbastanza esaustivo .

In pratica vedrai varie modifiche di queste strategie di base. Ma nessuno di questi è un tempo davvero costante! Non penso che sia possibile nel peggiore dei casi, mentre si utilizza una quantità limitata di memoria.


Interessante, devo leggere questo in dettaglio. Tuttavia, sembra che questi sistemi si occupino specificamente di allocazioni dimensionali non costanti, il che non è un problema con cui mi sto confrontando.
Stéphane Gimenez,

Giusto. Mi dispiace, ho letto troppo in fretta la tua domanda.
martedì

O(lgn)

s / blocco libero più piccolo / blocco libero all'indirizzo più piccolo /
rgrig

2

Potresti voler dare un'occhiata all'analisi ammortizzata e in particolare alle matrici dinamiche. Anche se le operazioni non vengono realmente eseguite a tempo costante in ogni fase, a lungo andare sembra che sia così.


2
E in che modo gli array dinamici possono aiutare con l'allocazione della memoria?
svick

Vuoi (de) allocare blocchi di celle contigue usando lo stesso tipo di algoritmo? L'intero file sarebbe un elenco collegato di blocchi sempre più grandi.
gallais,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.