2015-05-28 10 views
11

Sto cercando di capire i pool di memoria per la gestione della memoria, ma non riesco a trovare molto a riguardo, anche se sembra essere un meccanismo molto comune.Come funzionano i pool di memoria?

Tutto quello che so di questo è che "pool di memoria, chiamata anche allocazione blocchi di dimensione fissa" come dice Wikipedia, e posso usare quei pezzi di allocare memoria per i miei oggetti>

C'è qualche standard di specifiche sui pool di memoria?

Mi piacerebbe sapere come funziona questo heap, come può essere implementato e come dovrebbe essere utilizzato?

Un semplice esempio per mostrare come usarli sarebbe apprezzato

EDIT

Cos'è Pool?

L'allocazione di pool è uno schema di allocazione di memoria molto veloce, ma limitato nel suo utilizzo. Per ulteriori informazioni sull'allocazione del pool (anche chiamato semplice archiviazione segregata, vedere concetti concetti e Simple Archiviazione segregata).

da this question

riesco a capire cosa volesse dire, ma che non aiuta a capire come posso utilizzare e come posso pool di memoria aiutare la mia domanda, come viene implementato

+1

Dai un'occhiata a [boost :: pool] (http://www.boost.org/doc/libs/1_58_0/libs/pool/doc/html/index.html) – rds504

+0

Vedi anche: http: // stackoverflow.com/questions/16378306/c11-memory-pool-design-pattern – NathanOliver

+0

Forse questo aiuta: [Gestione della memoria] (https://msdn.microsoft.com/en-us/library/windows/desktop/aa366779%28v = vs.85% 29.aspx) – Zero

risposta

12

Qualsiasi tipo di "pool" è in realtà solo risorse che hai acquisito/inizializzato in anticipo in modo che siano già pronti per l'uso, non allocati al volo con ogni richiesta del cliente. Quando i client finiscono di usarli, la risorsa ritorna al pool invece di essere distrutta.

I pool di memoria sono fondamentalmente solo memoria allocata in anticipo (e in genere in blocchi grandi). Ad esempio, è possibile allocare in anticipo 4 kilobyte di memoria. Quando un client richiede 64 byte di memoria, gli basta passare un puntatore a uno spazio inutilizzato in quel pool di memoria per consentire loro di leggere e scrivere quello che vogliono. Quando il client ha finito, puoi semplicemente contrassegnare quella sezione della memoria come non utilizzata di nuovo.

Come esempio di base che non si preoccupa di allineamento, la sicurezza, o tornare memoria inutilizzata (liberata) al pool:

class MemoryPool 
{ 
public: 
    MemoryPool(): ptr(mem) 
    { 
    } 

    void* allocate(int mem_size) 
    { 
     assert((ptr + mem_size) <= (mem + sizeof mem) && "Pool exhausted!"); 
     void* mem = ptr; 
     ptr += mem_size; 
     return mem; 
    } 

private: 
    MemoryPool(const MemoryPool&); 
    MemoryPool& operator=(const MemoryPool&); 
    char mem[4096]; 
    char* ptr; 
}; 

... 
{ 
    MemoryPool pool; 

    // Allocate an instance of `Foo` into a chunk returned by the memory pool. 
    Foo* foo = new(pool.allocate(sizeof(Foo))) Foo; 
    ... 
    // Invoke the dtor manually since we used placement new. 
    foo->~Foo(); 
} 

Questo è efficace solo messa in comune memoria dalla pila.Un'implementazione più avanzata potrebbe concatenare i blocchi e fare alcune ramificazioni per vedere se un blocco è pieno per evitare l'esaurimento della memoria, gestire blocchi di dimensioni fisse che sono unioni (elenco dei nodi quando sono liberi, memoria per il client quando utilizzato) e ha sicuramente bisogno di gestire l'allineamento (il modo più semplice è semplicemente allineare i blocchi di memoria e aggiungere padding a ciascun blocco per allineare quello successivo).

più fantasia sarebbe allocatori amico, lastre, quelli che applicano algoritmi di adattamento, ecc Implementazione di un allocatore non è così diverso da una struttura dati, ma si ottiene ginocchia in bit e byte prime, devono pensare a cose come l'allineamento e non è in grado di riprodurre i contenuti in giro (non è possibile invalidare i puntatori esistenti nella memoria in uso). Come le strutture dati, non esiste uno standard d'oro che dice: "Farai questo". Ce n'è una grande varietà, ognuno con i propri punti di forza e di debolezza, ma ci sono alcuni algoritmi particolarmente popolari per l'allocazione della memoria.

L'implementazione degli allocatori è qualcosa che consiglierei a molti sviluppatori C e C++ solo per entrare in sintonia con il modo in cui la gestione della memoria funziona un po 'meglio. Può renderti un po 'più consapevole di come la memoria richiesta si colleghi alle strutture di dati che li utilizzano, e apre anche una nuova porta di opportunità di ottimizzazione senza utilizzare nuove strutture di dati. Può anche rendere le strutture dati come le liste concatenate che normalmente non sono molto efficienti e molto più utili e ridurre le tentazioni per rendere i tipi opachi/astratti meno opachi per evitare il sovraccarico dell'heap. Tuttavia, può esserci un'emozione iniziale che potrebbe voler farti calzare gli allocatori personalizzati per tutto, solo per rimpiangere in seguito l'onere aggiuntivo (specialmente se, nella tua eccitazione, ti dimentichi di problemi come la sicurezza dei thread e l'allineamento). Vale la pena prendersela comoda lì. Come con qualsiasi micro-ottimizzazione, generalmente è meglio applicare discretamente, a posteriori, e con un profiler in mano.

+1

Grazie, questa è esattamente la risposta che stavo cercando !! –

1

Fondamentalmente I pool di memoria consentono di evitare alcune spese di allocazione della memoria in un programma che alloca e libera la memoria frequentemente. Quello che fai è assegnare una grande porzione di memoria all'inizio dell'esecuzione e riutilizzare la stessa memoria per allocazioni diverse che non si sovrappongono temporalmente. È necessario disporre di un meccanismo per tenere traccia di quale memoria è disponibile e utilizzare quella memoria per le allocazioni. Quando hai finito con la memoria, invece di liberarla, contrassegnala come disponibile di nuovo.

In altre parole, invece di chiamate verso new/malloc e delete/free, effettuare una chiamata alle funzioni allocatore/deallocatore autodefiniti.

In questo modo è possibile eseguire solo un'allocazione (presupponendo di conoscere approssimativamente la quantità di memoria necessaria in totale) nel corso dell'esecuzione. Se il tuo programma è a latenza, piuttosto che legato alla memoria, puoi scrivere una funzione di allocazione che va più veloce di malloc a scapito dell'uso della memoria.

5

Il concetto di base di un pool di memoria consiste nell'allocare una grande porzione di memoria per l'applicazione e, in seguito, invece di utilizzare semplicemente new per richiedere memoria dall'O/S, si restituisce un blocco della memoria allocata in precedenza memoria invece.

Per fare in modo che questo funzioni, è necessario gestire l'utilizzo della memoria da soli e non può fare affidamento sull'O/S; Ad esempio, è necessario implementare le proprie versioni di new e delete e utilizzare le versioni originali solo durante l'allocazione, la liberazione o il ridimensionamento potenziale del proprio pool di memoria.

Il primo approccio sarebbe quello di definire la propria classe che incapsula un pool di memoria e fornisce metodi personalizzati che implementano la semantica di new e delete, ma prendono memoria dal pool pre-allocato. Ricorda, questo pool non è altro che un'area di memoria che è stata allocata utilizzando new e ha una dimensione arbitraria. La versione del pool di new/delete risp. Resp. prendere i puntatori. La versione più semplice sarebbe probabilmente simile al codice C:

void *MyPool::malloc(const size_t &size) 
void MyPool::free(void *ptr) 

È possibile aggiungere questo colore ai modelli per aggiungere automaticamente la conversione, ad es.

template <typename T> 
T *MyClass::malloc(); 

template <typename T> 
void MyClass::free(T *ptr); 

Si noti che, grazie agli argomenti di template, l'argomento size_t size possono essere omessi dal momento che il compilatore consente di chiamare sizeof(T) in malloc().

La restituzione di un puntatore semplice indica che il pool può crescere solo quando è disponibile memoria adiacente e si restringe solo se la memoria del pool ai suoi "bordi" non viene acquisita. Più specificamente, non è possibile riposizionare il pool perché ciò invaliderebbe tutti i puntatori restituiti dalla funzione malloc.

Un modo per correggere questa limitazione è di restituire i puntatori ai puntatori, ovvero restituire T** anziché semplicemente T*. Ciò consente di modificare il puntatore sottostante mentre la parte rivolta all'utente rimane la stessa. Incidentalmente, ciò è stato fatto per NeXT O/S, dove è stato chiamato "handle". Per accedere ai contenuti del manico, è stato necessario chiamare (*handle)->method() o (**handle).method(). Alla fine, Maf Vosburg ha inventato uno pseudo-operatore che ha sfruttato la precedenza degli operatori per sbarazzarsi della sintassi (*handle)->method(): handle[0]->method(); Si chiamava sprong operator.

I vantaggi di questa operazione sono: In primo luogo, si evita il sovraccarico di una chiamata tipica di new e delete, e in secondo luogo, il vostro pool di memoria assicura che un segmento contiguo di memoria è utilizzata dall'applicazione, cioè, evita frammentazione della memoria e quindi aumenta gli hit della cache della CPU.

Quindi, in pratica, un pool di memoria fornisce una velocità di guadagno con lo svantaggio di un codice di applicazione potenzialmente più complesso. Ma poi di nuovo, ci sono alcune implementazioni di pool di memoria che sono dimostrati e possono essere semplicemente utilizzati, come ad esempio boost::pool.

+0

Non penso che il codice più complesso sia in realtà lo svantaggio principale. Se fatto correttamente, il modulo allocatore dovrebbe essere più o meno autonomo (come dimostrato da boost :: pool). Penso che il principale svantaggio è la penalità di utilizzo della memoria necessaria per implementare i pool di memoria. – Daniel

+0

Avete qualche codice di una semplice implementazione di pool di memoria? –

+1

Non l'ho provato, ma questo sembra ragionevole: https://github.com/cacay/MemoryPool/blob/master/C-11/MemoryPool.tcc – Technaton