Heap: Freud und Leid des Entwicklers

Veröffentlicht: 14. Jun 2000 | Aktualisiert : 15. Jun 2004

Von Murali R. Krishnan

Beschreibung allgemeiner Probleme mit der Heapleistung und der Möglichkeiten, diese Probleme zu verhindern.

Auf dieser Seite

Einführung
Was ist ein Heap?
Hinweise zur Heapimplementierung
Allgemeine Probleme mit der Heapleistung
So schützen Sie sich vor Heapproblemen
Zusammenfassung

Einführung

Sind Sie ein rundum zufriedener Benutzer dynamisch zugeordneter C/C++-Objekte? Setzen Sie die Automatisierung häufig für die Kommunikation zwischen Modulen ein? Kann es sein, dass Ihr Programm aufgrund der Heapzuordnung langsam ist? Damit sind Sie nicht allein. Bei fast allen Projekten treten früher oder später Probleme mit dem Heap auf. Wenn dies der Fall ist, pflegen Entwickler zu sagen: "Der Heap ist nur zu langsam – mein Code ist eigentlich gut." Nun, das ist nur teilweise richtig. Es zahlt sich aus, sich mit dem Heap, seiner Verwendung und möglichen Problemen auseinander zu setzen.

Was ist ein Heap?

(Wenn Sie bereits wissen, was ein Heap ist, können Sie mit dem Abschnitt fortfahren.)
Ein Heap dient zum dynamischen Zuordnen und Freigeben von Objekten durch das Programm. Heapfunktionen werden in den folgenden Situationen verwendet:

  • Die Anzahl und die Größe der für das Programm erforderlichen Objekte ist vorab nicht bekannt.

  • Ein Objekt ist zu groß für einen Stackallocator.

Zur Laufzeit verwendet ein Heap für den Code und zum Stapeln Teile des Speichers, die außerhalb des zugeordneten Bereichs liegen. Die folgende Grafik zeigt die verschiedenen Schichten von Heapallocators.

Bild01

GlobalAlloc/GlobalFree: Microsoft® Win32®-Heapaufrufe, die direkt mit dem standardmäßigen Per-Process-Heap kommunizieren.
LocalAlloc/LocalFree: Win32-Heapaufrufe (aus Kompatibilitätsgründen mit Microsoft Windows NT®), die direkt mit dem standardmäßigen Per-Process-Heap kommunizieren.
COM-IMalloc-Allocator (oder CoTaskMemAlloc/CoTaskMemFree): Die Funktionen verwenden den standardmäßigen Per-Process-Heap. Für die Automatisierung wird der COM-Allocator (Component Object Model) und für Anforderungen der Per-Process-Heap verwendet.
C/C++-Runtime-Allocator (CRT): Bietet sowohl die Operatoren malloc() und free() als auch new und delete. Programmiersprachen wie Microsoft Visual Basic® und Java umfassen auch neue Operatoren und verwenden anstelle von Heaps die Garbagecollection. Der CRT-Allocator erstellt seinen eigenen privaten Heap, der auf den Win32-Heap aufsetzt.
In Windows NT ist der Win32-Heap eine dünne Schicht über dem Windows NT-Runtime-Allocator. Alle APIs leiten ihre Anforderungen an die NTDLL weiter.
Der Windows NT-Runtime-Allocator ist der wichtigste Heapallocator innerhalb von Windows NT. Er besteht aus einem Front-End-Allocator mit 128 Freilisten für Blockgrößen von 8 bis 1024 Byte. Der Back-End-Allocator reserviert und übernimmt Seiten mithilfe eines virtuellen Speichers.
Im unteren Teil des Diagramms befindet sich der Allocator für virtuellen Speicher, der vom Betriebssystem verwendete Seiten reserviert und übernimmt. Alle Allocators verwenden die virtuelle Speicherfunktion für den Zugriff auf Daten.
Müsste es nicht eigentlich einfach sein, Blöcke zuzuordnen und freizugeben? Warum sollte das so lange dauern?

Hinweise zur Heapimplementierung

Normalerweise werden das Betriebssystem und Runtimebibliotheken mit einer Heapimplementierung geliefert. Zu Beginn eines Prozesses erstellt das Betriebssystem einen standardmäßigen Heap, der als Prozessheap bezeichnet wird.Der Prozessheap wird zum Zuordnen von Blöcken verwendet, wenn kein anderer Heap eingesetzt wird. Sprachruntimes können innerhalb eines Prozesses auch separate Heaps erstellen. (Die C-Runtime erstellt z. B. einen eigenen Heap.) Neben diesen dedizierten Heaps kann das Anwendungsprogramm oder eine der vielen geladenen DLLs (Dynamic Link Libraries) separate Heaps erstellen und verwenden. Win32 bietet eine umfassende Reihe von APIs zum Erstellen und Verwenden privater Heaps. In der MSDN Library finden Sie einen hervorragenden Artikel zu Heap Functions (in Englisch) .

Wenn Anwendungen oder DLLs private Heaps erstellen, befinden sich diese im Prozessbereich und sind prozessweit zugänglich. Jede Datenzuordnung, die von einem Heap vorgenommen wird, sollte für diesen Heap freigegeben werden. (Die Zuordnung durch einen Heap und Freigabe für einen anderen Heap ist nicht sinnvoll.)
Der Heap ist in allen virtuellen Speichersystemen oberhalb des virtuellen Speicher-Managers angeordnet. Die Heaps von Sprachruntimes befinden sich ebenfalls über dem virtuellen Speicher. In einigen Fällen werden sie Betriebssystemheaps überlagert, die Runtimeheaps führen jedoch durch die Zuordnung großer Blöcke ihre eigene Speicherverwaltung aus. Wenn der Betriebssystemheap umgangen wird, um die virtuellen Speicherfunktionen zu verwenden, kann der Heap die Blöcke ggf. effizienter zuordnen und verwenden.
Typische Heapimplementierungen bestehen aus Front-End- und Back-End-Allocators. Der Front-End-Allocator verwaltet eine Freiliste von Blöcken fester Größe. Bei einem Zuordnungsaufruf versucht der Heap, einen freien Block in der Front-End-Liste zu finden. Wenn dieser Versuch fehlschlägt, ist der Heap gezwungen, einen großen Block vom Back-End zuzuordnen (Reservieren und Ausführen eines Commits von virtuellem Speicher), um die Anforderung auszuführen. Bei den meisten Implementierungen kommt es zu einem Overhead bei der Zuordnung pro Block, der zusätzliche Ausführungszyklen erfordert und den verfügbaren Speicherplatz verringert.

Der Knowledge Base-Artikel Q10758 "Managing Memory with calloc() and malloc()" (suchen Sie anhand der Artikelnummer) enthält weitere Hintergrundinformationen zu diesen Themen. Eine ausführliche Abhandlung zu Heapimplementierungen/-entwürfen finden Sie in "Dynamic Storage Allocation: A Survey and Critical Review" von Paul R. Wilson, Mark S. Johnstone, Michael Neely und David Boles in International Workshop on Memory Management,Kinross, Schottland, UK, September 1995 ( http://www.cs.utexas.edu/users/oops/papers.html) (in Englisch)*.*Die Windows NT-Implementierung (Windows NT Version 4.0 und höher) verwendet 127 Freilisten von 8-Byte-Blöcken mit einer Größe von 8 bis 1024 Byte sowie eine "Wundertütenliste". Die Wundertütenliste (Freiliste [0]) enthält Blöcke, die größer als 1024 Byte sind. Die Freiliste enthält miteinander verknüpfte Objekte in einer doppelt verknüpften Liste. Standardmäßig führt der Prozessheap Vereinigungen aus. (Beim Vereinigen werden benachbarte Blöcke kombiniert, um so einen größeren Block zu erstellen.) Für das Vereinigen sind zusätzliche Zyklen erforderlich, die interne Fragmentierung der Heapblöcke wird dadurch jedoch reduziert.
Durch eine globale Sperre wird der Heap vor Multithread-Verwendung geschützt. (Siehe hierzu den ersten Abschnitt in "Server Performance and Scalability Killers" von George Reilly im MSDN Online Web Workshop unter https://msdn.microsoft.com/workshop/server/iis/tencom.asp(in Englisch).) Diese Sperre dient zum Schutz der Heapdatenstruktur vor zufälligem Zugriff über mehrere Threads. Die Sperre kann sich jedoch nachteilig auf die Leistung auswirken, wenn zu häufig Heapoperationen ausgeführt werden.

Allgemeine Probleme mit der Heapleistung

Im Folgenden finden Sie eine Auflistung der häufigsten Probleme beim Arbeiten mit dem Heap:

  • Verlangsamung aufgrund von Zuordnungen. Das Zuordnen dauert einfach lange. Die wahrscheinlichste Ursache für die Verlangsamung ist, dass die Freilisten die angeforderten Blöcke nicht enthalten und der Code des Runtime-Allocators daher mehrere Zyklen mit der Suche nach einem größeren freien Block oder dem Zuordnen eines neuen Blocks vom Back-End-Allocator verbringt.

  • Verlangsamung aufgrund von Freigaben. Für Freigaben sind mehr Zyklen erforderlich, insbesondere dann, wenn die Vereinigung aktiviert wurde. Während der Vereinigung sollte jede freie Operation ihre Nachbarn "suchen", aus ihnen einen größeren Block erstellen und diesen dann wieder in die Freiliste einfügen. Während dieser Suche wird der Speicher u.U. in zufälliger Reihenfolge abgefragt, wodurch Cache belegt und die Leistung bzw. die Geschwindigkeit beeinträchtigt wird.

  • Verlangsamung aufgrund von Heapkonflikten. Konflikte treten auf, wenn zwei oder mehrere Threads versuchen, gleichzeitig auf Daten zuzugreifen. In diesem Fall müssen die Threads warten, bis der jeweils andere den Zugriff beendet hat, bevor sie fortfahren können. Konflikte führen immer zu Störungen; es ist mit Abstand das größte Problem bei Multiprozessorsystemen. Anwendungen oder DLLs mit starker Speicherblocknutzung werden bei der Ausführung mit mehreren Threads (und auf Multiprozessorsystemen) verlangsamt. Die Verwendung einer einzigen Sperre – die am häufigsten verwendete Lösung – führt dazu, dass alle Operationen, die den Heap verwenden, serialisiert werden. Dies hat zur Folge, dass die Threads den Kontext ändern, während sie auf die Sperre warten. Sie können sich das so vorstellen, als würde die Verlangsamung durch den Stop-and-go-Verkehr vor einer roten Ampel verursacht.

    Konflikte führen i.d.R. zu einer Kontextänderung der Threads und Prozesse. Kontextänderungen sind kostspielig und zeitaufwendig, noch kostspieliger ist jedoch der Verlust von Daten aus dem Prozessorcache und das erneute Erstellen dieser Daten, wenn der Thread anschließend wiederhergestellt wird.

  • Verlangsamung aufgrund von Heapbeschädigung. Eine Beschädigung tritt auf, wenn die Anwendung die Heapblöcke nicht ordnungsgemäß verwendet. Dies ist z. B. der Fall, wenn Blöcke zweimal freigegeben, nach der Freigabe verwendet oder Daten blockgrenzenübergreifend überschrieben werden. (Beschädigungen werden in diesem Artikel nicht behandelt. Ausführliche Informationen zum Überschreiben von Speicher und zu Speicherfehlern finden Sie in der Debugdokumentation zu Microsoft Visual C++® (in Englisch).)

  • Verlangsamung aufgrund häufiger Zuordnungen und erneuten Zuordnungen. Dieses Phänomen tritt bei Verwendung von Skriptsprachen sehr häufig auf. Die Zeichenfolgen werden wiederholt zugeordnet, nehmen mit diesen erneuten Zuordnungen an Umfang zu und werden dann freigegeben. Diese Vorgehensweise ist nicht zu empfehlen. Versuchen Sie, möglichst große Zeichenfolgen zuzuordnen und den Puffer zu verwenden. Ein Alternative hierzu ist es, Verkettungen zu minimieren.

Durch Konflikte kommt es bei der Zuordnung und Freigabe zu einer Verlangsamung. Ideal wäre ein konfliktfreier Heap und eine schnelle Zuordnung/Freigabe. Einen solchen Universalheap gibt es jedoch noch nicht, obwohl dies für die Zukunft nicht ausgeschlossen ist.
In allen Serversystemen (z. B. IIS, MSProxy, DatabaseStacks, Netzwerkserver, Exchange usw.) stellt die Heapsperre einen ernstzunehmenden Engpass dar. Je mehr Prozessoren verwendet werden, desto schwerwiegender sind die auftretenden Konflikte.

So schützen Sie sich vor Heapproblemen

Nachdem Sie nun die allgemeinen Heapprobleme kennen gelernt haben – wünschten Sie nicht, Sie hätten einen Zauberstab, mit dem Sie all diese Probleme verschwinden lassen könnten? Ich wünschte, so etwas gäbe es. Leider gibt es jedoch keinen solchen Zaubertrick – rechnen Sie also nicht damit, die Heapprozesse noch kurz vor Lieferung des Produkts beschleunigen zu können. Planen Sie Ihre Heapstrategie frühzeitig – das ist das Beste, was Sie tun können. Die optimale Strategie zum Verbessern der Leistung besteht darin, die Verwendung des Heaps zu ändern und die Anzahl von Heapoperationen zu reduzieren.
Wie reduzieren Sie die Anzahl von Heapoperationen? Sie können die Lokalität innerhalb der Datenstrukturen nutzen. Betrachten Sie hierzu das folgende Beispiel:

struct ObjectA { 
   // Daten für objectA 
} 
struct ObjectB { 
   // Daten für objectB 
} 
// Gemeinsames Verwenden von ObjectA und ObjectB 
// 
// Verwenden von Zeigern  
// 
struct ObjectB { 
   struct ObjectA * pObjA; 
   // Daten für objectB 
} 
// 
// Verwenden von Einbettung 
// 
struct ObjectB { 
   struct ObjectA pObjA; 
   // Daten für objectB 
} 
// 
// Aggregation – Verwenden von ObjectA und ObjectB innerhalb eines anderen Objekts 
// 
struct ObjectX { 
   struct ObjectA  objA; 
   struct ObjectB  objB; 
} 
  • Verknüpfen Sie Datenstrukturen nicht mithilfe von Zeigern. Wenn Sie Zeiger verwenden, um zwei Datenstrukturen zu verknüpfen, werden die Objekte A und B im oben stehenden Beispiel getrennt voneinander zugeordnet und freigegeben. Dies verursacht zusätzliche Kosten und ist die Vorgehenweise, die wir vermeiden möchten.

  • Betten Sie untergeordnete Objekte, auf die verwiesen wird, in übergeordnete Objekte ein. Wenn ein Objekt einen Zeiger enthält, bedeutet dies, dass wir ein dynamisches Element (80%) und einen neuen Speicherort haben, der dereferenziert werden muss. Durch das Einbetten wird die Lokalität erweitert und die Notwendigkeit weiterer Zuordnungen/Freigaben verringert. Auf diese Weise können Sie die Leistung der Anwendung verbessern.

  • Kombinieren Sie kleinere Objekte in einem großen Objekt (Aggregation). Durch Aggregation wird die Anzahl zugeordneter und freigegebener Blöcke reduziert. Wenn mehrere Entwickler an verschiedenen Teilen eines Entwurfs arbeiten, kann dies dazu führen, dass Sie viele kleine Objekte erhalten, die miteinander kombiniert werden können. Die eigentliche Herausforderung bei dieser Art der Integration besteht darin, die richtigen Aggregationsgrenzen zu finden.

  • Integrieren Sie einen Puffer, der für 80% Ihrer Anforderungen ausreicht (die sog. 80/20-Regel). In vielen Situationen müssen im Speicherpuffer Zeichenfolgen-/Binärdaten gespeichert werden, und die Gesamtanzahl von Byte ist vorab nicht bekannt. Sie sollten daher einen Puffer mit ausreichender Größe für 80% der anfallenden Aufgaben integrieren. Für die verbleibenden 20% können Sie einen neuen Puffer zuordnen und einen Zeiger auf diesen Puffer einrichten. Auf diese Weise reduzieren Sie die Anzahl von Zuordnungs- und Freigabeaufrufen und verringern die räumliche Lokalität der Daten, wodurch die Leistung des Codes eindeutig verbessert wird.

  • Ordnen Sie Objekte abschnittsweise zu (Abschnitterstellung). Die Abschnitterstellung ist eine Methode, mit der mehrere Objekte gleichzeitig als Gruppe zugeordnet werden können. Wenn Sie die Änderungen einer Liste von Elementen protokollieren müssen, z. B. einer Liste von {Name, Wert-} Paaren, haben Sie zwei Möglichkeiten: Möglichkeit 1 ist, einen Knoten pro Name/Wert-Paar zuzuordnen. Bei Möglichkeit 2 wird eine Struktur zugeordnet, die z. B. fünf Name/Wert-Paare enthalten kann. Wenn Sie in einem Szenario z. B. vier Paare speichern, würden die Anzahl von Knoten und der zusätzlich erforderliche Speicherplatz für weitere listenverknüpfte Zeiger reduziert werden.

    Die Abschnitterstellung schont aufgrund der damit verbundenen erweiterten Lokalität den Prozessorcache, insbesondere den L1-Cache. Wichtig ist hierbei auch, dass sich bei abschnittsweisen Zuordnungen einige Datenblöcke in derselben virtuellen Seite befinden.

  • Verwenden Sie "_amblksiz" in geeigneter Weise. Die C-Runtime (CRT) verfügt über einen benutzerdefinierten Front-End-Allocator, der Blöcke entsprechend der für _amblksiz festgelegten Größe vom Back-End (Win32-Heap) zuordnet. Wenn _amblksiz auf einen höheren Wert gesetzt wird, kann dies die Anzahl von Aufrufen reduzieren, die an das Back-End erfolgen. Dies gilt nur für Programme, die die CRT umfassend nutzen.

Die Vorteile, die sich durch die Verwendung der o.g. Methoden ergeben, sind je nach Objekttyp, -größe und Arbeitsauslastung unterschiedlich. Leistung und Skalierbarkeit werden jedoch in jedem Fall verbessert. Ein Nachteil ist der spezialisierte Code, der – sofern er sorgfältig durchdacht wird – aber gut zu verwalten ist.

Weitere Möglichkeiten zum Verbessern der Leistung
Im Folgenden finden Sie einige weitere Techniken, mit denen Sie die Geschwindigkeit des Heaps verbessern können:

  • Verwenden Sie den Windows NT5-Heap Dank dem Bemühen und der harten Arbeit mehrerer Leute konnten Anfang 1998 einige bedeutende Verbesserungen an Microsoft Windows® 2000 vorgenommen werden:

    • Verbesserte Sperren im Heapcode. Im Heapcode wird eine Sperre pro Heap verwendet. Diese globale Sperre dient dazu, die Heapdatenstruktur vor Multithread-Verwendung zu schützen. Leider kann es bei einem Heap in Szenarios mit starkem Verkehr noch immer zu Störungen kommen – das Ergebnis ist eine höhere Konfliktanfälligkeit und schlechtere Leistung. Bei Windows 2000 wurde der kritische Bereich des Sperrencodes so reduziert, dass die Wahrscheinlichkeit von Konflikten minimiert und die Skalierbarkeit verbessert wird.

    • Verwendung von Nachschlaglisten. Die Heapdatenstruktur verwendet einen schnellen Cache für alle freien Blöcke mit einer Größe zwischen 8 und 1024 Byte (in Inkrementen von jeweils 8 Byte). Der schnelle Cache wurde ursprünglich durch die globale Sperre geschützt. Jetzt werden Nachschlaglisten für den Zugriff auf die Freiliste des schnellen Cache verwendet. Diese Listen erfordern keine Sperren und setzen stattdessen 64-Bit-Interlock-Operationen ein, wodurch die Leistung verbessert wird.

    • Interne Datenstrukturalgorithmen wurden ebenfalls verbessert.

    Durch diese Verbesserungen entfällt zwar die Notwendigkeit, Zuordnungscaches zu verwenden; weitere Optimierungen werden dadurch jedoch nicht ausgeschlossen. Beurteilen Sie Ihren Code mit dem Windows NT5-Heap; er sollte sich für Blöcke von weniger als 1024 Byte (1 KB) optimal eignen (Blöcke vom Front-End-Allocator). GlobalAlloc() und LocalAlloc() basieren auf demselben Heap und sind geeignete Mechanismen für den Zugriff auf die Per-Process-Heaps. Verwenden Sie Heap*-APIs, um auf den Per-Process-Heap zuzugreifen, oder erstellen Sie für Zuordnungen einen eigenen Heap, wenn lokal eine hohe Leistung erforderlich ist. Sie können die Operationen VirtualAlloc()/VirtualFree() bei Bedarf für Operationen mit großen Blöcken auch direkt verwenden.

    Die beschriebenen Verbesserungen wurden in Windows 2000 Beta 2 und Windows NT 4.0 SP4 implementiert. Nach diesen Änderungen konnte eine erhebliche Abnahme der Heapsperrenkonflikte festgestellt werden. Davon können alle direkten Benutzer von Win32-Heaps profitieren. Der CRT-Heap basiert auf dem Win32-Heap, verwendet jedoch einen eigenen Heap für kleine Blöcke und kann daher die Vorteile der Änderungen in Windows NT nicht nutzen. (Visual C++ Version 6.0 verfügt ebenfalls über einen verbesserten Heapallocator.)

  • Verwenden Sie Zuordnungscaches

    Mithilfe eines Zuordnungscaches können Sie Blöcke für die spätere Verwendung zwischenspeichern. Auf diese Weise kann die Anzahl von Zuordnungs-/Freigabeaufrufen an den Prozessheap (oder globalen Heap) reduziert werden. Blöcke, die einmal zugeordnet wurden, können so oft wie möglich wieder verwendet werden. Außerdem ermöglichen Zuordnungscaches die Erstellung von Statistiken, mit denen die Objektverwendung auf höherer Ebene verständlicher dargestellt werden kann.
    Normalerweise wird ein benutzerdefinierter Heapallocator oberhalb des Prozessheaps implementiert. Der benutzerdefinierte Heapallocator verhält sich weitgehend wie der Systemheap. Der wesentliche Unterschied ist, dass er über dem Prozess einen Cache für die zugeordneten Objekte bereitstellt. Die Caches sind für feste Größen ausgelegt (z. B. 32 Byte, 64 Byte, 128 Byte usw.). Dies ist eine gute Strategie, jedoch fehlen dieser Art von benutzerdefiniertem Heapallocator die semantischen Informationen zu den zugeordneten und freigegebenen Objekten.
    Im Gegensatz zu benutzerdefinierten Heapallocators werden Zuordnungscaches als Caches für jeweils eine Klasse implementiert. Sie können viele semantische Informationen enthalten und bieten darüber hinaus alle Vorteile des benutzerdefinierten Heapallocators. Jeder Zuordnungscache-Handler ist mit einem Objekt im binären Zielelement verknüpft. Er kann mit einer Gruppe von Parametern initialisiert werden, die Parallelitätsebene, Größe des Objekts, Anzahl von Elementen, die in der Freiliste gespeichert werden sollen, usw. angeben. Der Zuordnungscache-Handler unterhält einen eigenen privaten Pool freigegebener Entitäten (entsprechend dem festgelegten Grenzwert) und verwendet zum Schutz eine private Sperre. Der Zuordnungscache und die privaten Sperren reduzieren gemeinsam den Verkehr zum Hauptsystemheap und verbessern so Parallelität, Wiederverwendbarkeit und Skalierbarkeit.
    Zum regelmäßigen Überprüfen der Aktivität aller Zuordnungscache-Handler und zum Freigeben nicht verwendeter Ressourcen ist ein Aufräumdienst erforderlich. Wenn keine Aktivität festgestellt wird, kann der Pool zugeordneter Objekte freigegeben und so die Leistung verbessert werden.
    Jede Zuordnungs-/Freigabeaktivität kann überwacht werden. Die erste Informationsebene umfasst die Gesamtanzahl von Objekten und ausgeführten Zuordnungs- bzw. Freigabeaufrufen. Die semantische Beziehung zwischen verschiedenen Objekten kann anhand der dazugehörigen Statistiken abgeleitet werden. Wenn diese Beziehung bekannt ist, kann mithilfe der oben beschriebenen Methoden die Speicherzuordnung reduziert werden.
    Zuordnungscaches dienen auch als Debughilfe beim Protokollieren der Anzahl von Objekten, die nicht korrekt bereinigt wurden. Anhand der dynamischen Stapelprotokolle und Signaturen können nicht nur die nicht bereinigten Objekte, sondern auch die fehlerhaften Aufrufer festgestellt werden.

  • MP Heap MP Heap ist ein Paket für die verteilte Zuordnung in Multiprozessorumgebungen und ist im Win32 SDK (Windows NT 4.0 und höher) erhältlich. Ursprünglich durch JVert implementiert, wurde die Heapabstraktion hier basierend auf dem Win32-Heappaket erstellt. MP Heap erstellt mehrere Win32-Heaps und versucht, Zuordnungsaufrufe auf verschiedene Heaps zu verteilen, um so die Konflikte bei einzelnen Sperren zu reduzieren.
    Dieses Paket ist ein Schritt in die richtige Richtung – eine Art verbesserter MP-kompatibler benutzerdefinierter Heapallocator. Es bietet jedoch weder semantische Informationen noch Statistiken. MP Heap wird häufig als SDK-Bibliothek verwendet. Wenn Sie mit diesem SDK eine wieder verwendbare Komponente erstellen, hat dies erhebliche Vorteile für Sie. Wenn Sie diese SDK-Bibliothek in jede DLL integrieren, vergrößern Sie jedoch Ihr Workingset.

  • Überdenken Sie Algorithmen und Datenstrukturen
    Damit auf Multiprozessorsystemen eine Skalierung möglich ist, müssen Algorithmen, Implementierung, Datenstrukturen und Hardware dynamisch skaliert werden können. Betrachten Sie einmal die Datenstrukturen, die am häufigsten zugeordnet und freigegeben werden, und stellen Sie sich die Frage: "Kann ich diese Aufgabe mit einer anderen Datenstruktur ausführen?" Wenn Sie z. B. eine Liste von schreibgeschützten Elementen haben, die bei der Initialisierung der Anwendung geladen werden, muss diese Liste nicht linear verknüpft sein. Sie können auch ein dynamisch zugeordnetes Array verwenden. Durch ein dynamisch zugeordnetes Array wird die Anzahl von Heapblöcken im Speicher verringert, die Fragmentierung reduziert und daher die Leistung verbessert.
    Wenn die erforderliche Anzahl kleiner Objekte reduziert wird, senkt dies auch die Belastung des Heapallocators. Angenommen, wir verwenden auf dem kritischen Verarbeitungspfad des Servers fünf verschiedene Objekte, die alle separat zugeordnet und freigegeben werden. Wenn diese Objekte zusammen zwischengespeichert werden, wird die Anzahl von Heapaufrufen von fünf auf eins verringert und somit die Belastung des Heaps bedeutend gesenkt. Dies gilt insbesondere dann, wenn mehr als 1.000 Anfragen pro Sekunde verarbeitet werden.
    Wenn Sie die Automatisierungsstrukturen umfassend nutzen, sollten Sie in Betracht ziehen, die Automatisierungs-BSTRs aus dem Hauptcode zu nehmen, oder zumindest wiederholte BSTR-Operationen vermeiden. (BSTR-Verkettung führt zu übermäßig vielen erneuten Zuordnungen und Zuordnungs-/Freigabeoperationen.)

Zusammenfassung

Heapimplementierungen neigen dazu, sehr allgemein für alle Plattformen ausgelegt zu sein, und verursachen daher einen starken Overhead. Für jeden Code gelten bestimmte Anforderungen. Die in diesem Artikel beschriebenen Methoden zum Reduzieren der Heapinteraktion können jedoch durch den Entwurf berücksichtigt werden.

  • Überprüfen Sie die Verwendung des Heaps in Ihrem Code.

  • Reduzieren Sie die Anzahl von Heapaufrufen im Code: Analysieren Sie kritische Pfade und feste Datenstrukturen.

  • Nehmen Sie entsprechende Messungen vor, um die Kosten der Heapaufrufe zu berechnen, bevor Sie benutzerdefinierte Wrapper implementieren.

  • Wenn Sie mit der Leistung unzufrieden sind, bitten Sie die Betriebssystem-Gruppe, den Heap zu verbessern. Je mehr diesbezügliche Anfragen eingehen, desto wahrscheinlicher ist es, dass der Heap irgendwann verbessert wird.

  • Bitten Sie die C-Runtime-Gruppe, die Allocators als Wrapper für die vom Betriebssystem bereitgestellten Heaps auszulegen. Auf diese Weise werden die Kosten für Heapaufrufe in der C-Runtime reduziert, wenn der Betriebssystemheap verbessert wird.

  • Heapverbesserungen werden fortlaufend im Betriebssystem vorgenommen (Windows NT-Familie). Halten Sie sich auf dem Laufenden, und nutzen Sie diese Verbesserungen.