2015-05-28 2 views
11

Ich versuche Speicherpools für Speicherverwaltung zu verstehen, aber ich kann nicht viel darüber finden, obwohl es scheint, ein sehr allgemeiner Mechanismus zu sein.Wie funktionieren Speicherpools?

Alles, was ich darüber weiß, ist, dass „Memory Pools, auch Blöcke fester Größe Zuteilung genannt“ wie sagt Wikipedia, und ich kann diese Stücke verwenden, um Speicher für meine Objekte>

Gibt es eine Standard zuweisen Spezifikationen zu Speicherpools?

Ich würde gerne wissen, wie funktioniert das auf dem Haufen, wie kann dies implementiert werden, und wie sollte dies verwendet werden?

Ein einfaches Beispiel zu zeigen, wie sie benutzen würde

EDIT

Was Pool ist geschätzt werden?

Pool-Zuweisung ist ein Speicherzuweisungsschema, das sehr schnell ist, aber begrenzt in seiner Verwendung. Weitere Informationen zur Poolzuordnung (auch genannt einfacher segregierter Speicher, siehe Konzepte Konzepte und Simple Segregated Storage).

von this question

kann ich verstehen, was er meinte, aber das hilft mir nicht verstehen, wie kann ich sie nutzen und wie kann meine Anwendung Speicherpools helfen, wie es

+1

Werfen Sie einen Blick auf [boost :: pool] (http://www.boost.org/doc/libs/1_58_0/libs/pool/doc/html/index.html) – rds504

+0

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

+0

Vielleicht hilft das: [Speicherverwaltung] (https://msdn.microsoft.com/en-us/library/windows/desktop/aa366779%28v = vs.85% 29.aspx) – Zero

Antwort

12

Jede Art von "Pool" ist wirklich nur Ressourcen, die Sie im Voraus erworben/initialisiert haben, so dass sie bereits bereit sind, zu gehen und nicht bei jeder Kundenanforderung zu vergeben. Wenn Clients ihre Verwendung beenden, kehrt die Ressource in den Pool zurück, anstatt zerstört zu werden.

Speicherpools sind im Grunde nur Speicher, den Sie im Voraus zugewiesen haben (und normalerweise in großen Blöcken). Beispielsweise könnten Sie im Voraus 4 Kilobyte Speicher reservieren. Wenn ein Client 64 Byte Speicher anfordert, übergeben Sie ihm einfach einen Zeiger auf einen unbenutzten Speicherplatz in diesem Speicherpool, damit er lesen und schreiben kann, was er will. Wenn der Client fertig ist, können Sie diesen Speicherbereich einfach als nicht mehr verwendet markieren.

Als einfaches Beispiel, das mit Ausrichtung, Sicherheit nicht stört, oder nicht verwendete Rückkehr (frei) Speicher zurück zum 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(); 
} 

Diese effektiv Speicher nur die Bündelung von dem Stapel.Eine komplexere Implementierung könnte Blockierungen zusammenketten und einige Verzweigungen ausführen, um zu sehen, ob ein Block voll ist, um nicht zu viel Speicher zu verbrauchen, mit Blöcken fester Größe umgehen, die Vereinigungen sind (freie Knoten, Speicher für den Client) es muss definitiv mit der Ausrichtung umgehen (am einfachsten ist es, die Speicherblöcke auszurichten und jedem Block einen Padding hinzuzufügen, um den nächsten auszurichten).

Mehr Phantasie wäre Buddy Allokatoren, Platten, diejenigen, die Anpassung Algorithmen, etc. Implementieren eines Zuordners ist nicht so verschieden von einer Datenstruktur, aber Sie knietief in rohen Bits und Bytes, müssen über Dinge wie Ausrichtung denken , und kann den Inhalt nicht mischen (vorhandene Zeiger auf den verwendeten Speicher können nicht ungültig gemacht werden). Wie Datenstrukturen gibt es nicht wirklich einen goldenen Standard, der sagt: "Du sollst das tun". Es gibt eine Vielzahl von ihnen, jede mit ihren eigenen Stärken und Schwächen, aber es gibt einige besonders beliebte Algorithmen für die Speicherzuweisung.

Das Implementieren von Zuordnungen ist etwas, was ich vielen C- und C++ - Entwicklern empfehlen würde, um mit der Art und Weise, wie die Speicherverwaltung ein bisschen besser funktioniert, aufzufrischen. Es kann Ihnen ein wenig bewusster machen, wie der angeforderte Speicher mit den Datenstrukturen, die diese verwenden, verbunden ist, und eröffnet auch eine ganz neue Tür für Optimierungsmöglichkeiten, ohne neue Datenstrukturen zu verwenden. Es kann auch Datenstrukturen wie verknüpfte Listen, die normalerweise nicht sehr effizient sind, viel nützlicher machen und Versuchungen reduzieren, undurchsichtige/abstrakte Typen undurchsichtiger zu machen, um den Heap-Overhead zu vermeiden. Es kann jedoch eine anfängliche Aufregung geben, die Sie dazu bringen könnte, benutzerdefinierte Allokatoren für alles zu schuhen, nur um später die zusätzliche Belastung zu bereuen (besonders wenn Sie in Ihrer Aufregung Probleme wie Fadensicherheit und Ausrichtung vergessen). Es lohnt sich, sich dort zu entspannen. Wie bei jeder Mikrooptimierung wird sie im Allgemeinen am besten im Nachhinein und mit einem Profiler in der Hand angewendet.

+1

Danke, das ist genau die Antwort, die ich suchte !! –

1

Grundsätzlich implementiert Mit Speicherpools können Sie die Kosten für die Speicherzuweisung in einem Programm vermeiden, das häufig Speicher zuweist und freigibt. Was Sie tun, ist, einen großen Teil des Speichers zu Beginn der Ausführung zuzuweisen und den gleichen Speicher für verschiedene Zuordnungen zu verwenden, die sich zeitlich nicht überlappen. Sie müssen über einen Mechanismus verfügen, mit dem Sie verfolgen können, welcher Speicher verfügbar ist, und diesen Speicher für Zuweisungen verwenden. Wenn Sie mit dem Speicher fertig sind, markieren Sie ihn wieder als verfügbar, anstatt ihn freizugeben.

Mit anderen Worten, anstelle von Anrufen zu new/malloc und delete/free, um einen Anruf zu Ihren selbstdefinierten Allocator/deallocator Funktionen machen.

Dadurch können Sie nur eine Zuweisung durchführen (vorausgesetzt, Sie wissen ungefähr, wie viel Speicher Sie insgesamt benötigen) während der Ausführung. Wenn Ihr Programm latenz- statt speichergebunden ist, können Sie eine Zuweisungsfunktion schreiben, die schneller als malloc auf Kosten einer gewissen Speicherauslastung ausgeführt wird.

5

Das Grundkonzept eines Speicherpools besteht darin, einen großen Teil des Speichers für Ihre Anwendung zuzuweisen, und statt new zum Abrufen von Speicher vom O/S zu verwenden, geben Sie einen Teil des zuvor zugewiesenen Speichers zurück Speicher stattdessen.

Damit dies funktioniert, müssen Sie die Speichernutzung selbst verwalten und sich nicht auf das Betriebssystem verlassen können. Sie müssen also Ihre eigenen Versionen von new und delete implementieren und die ursprünglichen Versionen nur verwenden, wenn Sie Ihren eigenen Speicherpool zuweisen, freigeben oder möglicherweise ändern.Der erste Ansatz wäre, die eigene Klasse zu definieren, die einen Speicherpool kapselt und benutzerdefinierte Methoden bereitstellt, die die Semantik von new und delete implementieren, aber Speicher aus dem vorab zugeordneten Pool übernehmen. Denken Sie daran, dieser Pool ist nichts anderes als ein Speicherbereich, der mit new zugewiesen wurde und eine beliebige Größe hat. Die Poolversion new/delete return resp. Nimm Zeiger. Die einfachste Version würde wahrscheinlich wie C-Code aussehen:

Sie können dies mit Vorlagen pfeffern, um die Konvertierung automatisch hinzuzufügen, z.

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

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

Beachten Sie, dass dank der Vorlage Argumente, die size_t size Argument weggelassen werden kann, da der Compiler ermöglicht es Ihnen sizeof(T) in malloc() zu nennen.

Das Zurückgeben eines einfachen Zeigers bedeutet, dass Ihr Pool nur wachsen kann, wenn angrenzender Speicher verfügbar ist, und nur verkleinert, wenn der Poolspeicher an seinen "Grenzen" nicht belegt ist. Genauer gesagt können Sie den Pool nicht verschieben, da dies alle Zeiger ungültig machen würde, die Ihre malloc-Funktion zurückgegeben hat.

Eine Möglichkeit, diese Einschränkung zu beheben, besteht darin, Zeiger auf Zeiger zurückzugeben, d. H. T** anstelle von einfach T* zurückzugeben. Dadurch können Sie den zugrunde liegenden Zeiger ändern, während der benutzerorientierte Teil gleich bleibt. Dies wurde übrigens für das NeXT O/S getan, wo es als "Handle" bezeichnet wurde. Um auf den Inhalt des Griffs zuzugreifen, musste man (*handle)->method() oder (**handle).method() aufrufen. Schließlich erfand Maf Vosburg einen Pseudooperator, der Operatorenpriorität ausnutzte, um die (*handle)->method() Syntax zu entfernen: handle[0]->method(); Es wurde sprong operator genannt.

Die Vorteile dieser Operation sind: Erstens, Sie den Aufwand eines typischen Aufruf new und delete, vermeiden und zweitens sorgt für Ihren Speicher-Pool, dass ein zusammenhängendes Speichersegment von der Anwendung verwendet wird, das heißt, es vermeidet Speicherfragmentierung und erhöht daher CPU-Cache-Treffer.

Im Grunde bietet Ihnen ein Speicherpool eine Beschleunigung, die Sie mit dem Nachteil eines potenziell komplexeren Anwendungscodes gewinnen. Aber es gibt auch einige Implementierungen von Speicherpools, die sich bewährt haben und einfach verwendet werden können, wie zum Beispiel boost::pool.

+0

Ich glaube nicht, komplexere Code ist wirklich der wichtigste Nachteil. Wenn es richtig gemacht wird, sollte das Zuweisungsmodul mehr oder weniger in sich geschlossen sein (wie durch boost :: pool bewiesen). Ich denke, der Hauptnachteil ist die Speichernutzungsstrafe, die notwendig ist, um Speicherpools zu implementieren. – Daniel

+0

Haben Sie Code einer einfachen Implementierung von Speicherpools? –

+1

Ich habe es nicht getestet, aber das sieht vernünftig aus: https://github.com/cacay/MemoryPool/blob/master/C-11/MemoryPool.tcc – Technaton