8 Stimmen

Heap-Fragmentierung und Windows-Speicherverwaltung

Ich habe Probleme mit der Speicherfragmentierung in meinem Programm und bin nicht in der Lage, sehr große Speicherblöcke nach einer Weile zuzuweisen. Ich habe die entsprechenden Beiträge in diesem Forum gelesen - hauptsächlich este ein. Und ich habe noch einige Fragen.

Ich habe einen Speicherplatz verwendet Profiler um sich ein Bild von der Erinnerung zu machen. Ich habe ein 1-zeiliges Programm geschrieben, das cin >> var; enthält und ein Bild des Speichers gemacht:

Alt-Text http://img22.imageshack.us/img22/6808/memoryk.gif Wo auf dem oberen Bogen - grün bedeutet freien Platz, gelb zugewiesen, rot gebunden. Meine Frage ist: Was ist der zugewiesene Speicher auf der rechten Seite? Ist es der Stack für den Hauptthread? Dieser Speicher wird nicht freigegeben, und er teilt den kontinuierlichen Speicher, den ich brauche. In diesem einfachen 1-Zeilen-Programm ist die Aufteilung nicht so schlimm. In meinem eigentlichen Programm ist mehr Speicher in der Mitte des Adressraums zugewiesen, und ich weiß nicht, woher er kommt. Ich weise diesen Speicher noch nicht zu.

  1. Wie kann ich versuchen, dieses Problem zu lösen? Ich dachte an einen Wechsel zu etwas wie nedmalloc oder dlmalloc. Das würde aber nur für die Objekte gelten, die ich selbst explizit zuweise, während die im Bild gezeigte Aufteilung nicht verschwinden würde? Oder gibt es eine Möglichkeit, die CRT-Zuweisung durch einen anderen Speichermanager zu ersetzen?

  2. Apropos Objekte, gibt es irgendwelche Wrapper für nedmalloc für C++, so dass ich neue und löschen verwenden können, um Objekte zuzuweisen?

Gracias.

1 Stimmen

Microsoft Security Essentials geht davon aus, dass die in der ursprünglichen Frage verlinkte "Profiler"-Anwendung den Trojaner Win32.Bisar!rts enthält.

12voto

CB Bailey Punkte 693084

Zunächst einmal danke ich Ihnen, dass Sie mein Tool nutzen. Ich hoffe, Sie finden es nützlich und können uns gerne Funktionswünsche oder Beiträge schicken.

Typischerweise werden Thin Slices an festen Punkten im Adressraum durch verknüpfte DLLs verursacht, die an ihrer bevorzugten Adresse geladen werden. Diejenigen, die weit oben im Adressraum geladen werden, sind in der Regel Microsoft-Betriebssystem-DLLs. Es ist für das Betriebssystem effizienter, wenn diese alle an ihren bevorzugten Adressen geladen werden können, da dann die schreibgeschützten Teile der DLLs von allen Prozessen gemeinsam genutzt werden können.

Der Ausschnitt, den Sie sehen können, ist kein Grund zur Sorge, er schneidet kaum etwas aus Ihrem Adressraum heraus. Wie Sie bemerkt haben, gibt es jedoch Dlls, die an anderen Stellen im Adressraum geladen werden. IIRC shlwapi.dll ist ein besonders schlechtes Beispiel, da sie bei etwa 0x2000000 geladen wird (wiederum IIRC), wodurch oft ein großer Teil des verfügbaren Adressraums in zwei kleinere Teile aufgeteilt wird. Das Problem dabei ist, dass Sie, sobald die DLL geladen ist, nichts mehr tun können, um diesen zugewiesenen Speicherplatz zu verschieben.

Wenn Sie gegen die DLL linken (entweder direkt oder über eine andere DLL), können Sie nichts tun. Wenn Sie LoadLibrary kann man sich schlau machen und seine bevorzugte Adresse reservieren und ihn zwingen, an einen anderen Ort - häufig einen besseren im Adressraum - verschoben zu werden, bevor man den reservierten Speicher freigibt. Das funktioniert allerdings nicht immer.

Unter der Haube verwendet Address Space Monitor VirtualQueryEx um den Adressraum des Prozesses zu untersuchen, aber es gibt einen weiteren Aufruf aus der psapi-Bibliothek, den andere Werkzeuge verwenden (z. B. Prozess-Explorer ), die Ihnen zeigen kann, welche Dateien (einschließlich DLLs) in welchen Teilen des Adressraums abgebildet sind.

Wie Sie festgestellt haben, kann der Platz in einem 2-GB-Benutzeradressraum erschreckend schnell erschöpft sein. Grundsätzlich besteht der beste Schutz gegen Speicherfragmentierung darin, einfach keine großen zusammenhängenden Speicherblöcke zu benötigen. Auch wenn es schwierig ist, eine Nachrüstung vorzunehmen, können Sie Ihre Anwendung so konzipieren, dass sie mit "mittelgroßen" Blöcken arbeitet, was in der Regel eine wesentlich effizientere Nutzung des Adressraums ermöglicht.

In ähnlicher Weise können Sie eine Auslagerungsstrategie verwenden, möglicherweise unter Verwendung von Memory-Mapped-Dateien oder Erweiterungen für Adressfenster .

0 Stimmen

Hallo und danke für das tolle Tool und den Hinweis auf die Funktion des Process Explorers.

0 Stimmen

@Charles Bailey: Re statische Verknüpfung - Könnte eine DLL nicht rebased werden?

0 Stimmen

Ja, Sie können DLLs neu basen, und das puede helfen, die Fragmentierung des Adressraums und die Ladezeiten zu optimieren, aber ... typischerweise sollten Sie dies nur mit DLLs tun, die Sie besitzen, und wenn Sie zu viele Mikro-Optimierungen vornehmen, enden Sie mit einer Reihe von DLLs, die in einer bestimmten Exe zu einem bestimmten Zeitpunkt gut funktionieren, aber für jede andere Exe nicht so gut eine optimierte Ladestrategie haben. Wenn Ihre DLLs neu erstellt werden und sich die Größe ändert, müssen Sie den Prozess erneut durchlaufen. Es kann also bis zu einem gewissen Grad funktionieren, aber wenn Sie darauf zurückgreifen müssen, können Sie in einem Teufelskreis mit hohem Wartungsaufwand enden.

2voto

Timo Geusch Punkte 23597

Ich nehme an, dass Sie häufig Objekte unterschiedlicher Größe zuweisen und freigeben und dass dies zu Ihren Speicherfragmentierungsproblemen führt?

Es gibt verschiedene Strategien, diese zu umgehen; die verschiedenen von Ihnen erwähnten Speichermanager könnten helfen, wenn sie das Fragmentierungsproblem für Sie lösen können, aber das würde eine etwas genauere Analyse der zugrunde liegenden Ursachen für die Fragmentierung erfordern. Wenn Sie zum Beispiel häufig Objekte von drei oder vier Typen zuweisen und diese das Problem der Speicherfragmentierung verschlimmern, sollten Sie diese in eigene Speicherpools einteilen, um die Wiederverwendung von Speicherblöcken der richtigen Größe zu ermöglichen. Auf diese Weise sollten Sie eine Reihe von Speicherblöcken zur Verfügung haben, die für dieses spezielle Objekt geeignet sind, und verhindern, dass bei der Zuweisung von Objekt X ein Speicherblock, der groß genug ist, um Y aufzunehmen, so aufgeteilt wird, dass Sie plötzlich keine Ys mehr zuweisen können.

Was (2) betrifft, so ist mir kein Wrapper um nedmalloc bekannt (ehrlich gesagt bin ich mit nedmalloc nicht sehr vertraut), aber Sie können Ihre eigenen Wrapper sehr leicht erstellen, da Sie entweder klassenspezifische Operatoren new und delete erstellen oder sogar die globalen Operatoren new und delete überladen/ersetzen können. Ich bin kein großer Fan von letzterem, aber wenn Ihr Allokations-"Hotspot" aus einer Handvoll Klassen besteht, ist es in der Regel ziemlich einfach, diese mit ihren eigenen, klassenspezifischen Operatoren new und delete nachzurüsten.

Das heißt, nedmalloc rechnet sich als Ersatz für die Standard-malloc/free und zumindest mit den MS-Compilern, ich denke, die C++-Laufzeitbibliothek wird new/delete zu malloc/free weiterleiten, so könnte es auch nur ein Fall von Gebäude Ihre ausführbare Datei mit nedmalloc sein.

2voto

Alan Punkte 13182

Um die Speicherfragmentierung zu verringern, können Sie die Vorteile der Windows Heap mit geringer Fragmentierung . Wir haben dies in unserem Produkt mit gutem Erfolg eingesetzt und hatten seitdem nicht annähernd so viele speicherbezogene Probleme.

0 Stimmen

Ich habe diese Funktion gesehen. Um sie zu aktivieren, müssen Sie jedoch HeapSetInformation() ausführen. Der Schnappschuss des Speichers wurde gleich in der ersten Zeile von main() gemacht und der Speicher ist bereits nach den ersten 1,3 GB des Adressraums fragmentiert. Nach einem Blick in den Prozess-Explorer sind es DLLs und andere Dinge. LFH kann also helfen, aber es verhindert nicht die Fragmentierung, die bereits durch das Laden von DLLs auftritt.

1voto

Goz Punkte 59671

Könnte es an der ausführbaren Datei liegen? Sie muss irgendwo in den Adressraum geladen werden ....

Wie für 2 seine ziemlich einfach zu überschreiben die globale neue und löschen Funktionen ... nur definieren Sie sie.

1voto

Andrew Sandoval Punkte 11

Der beste Weg, um herauszufinden, wo in Ihrem Programm Speicher zugewiesen ist, ist die Verwendung eines Debuggers. Es gibt Zuweisungen für jede geladene DLL und die ausführbare Datei selbst, und alle von ihnen fragmentieren den virtuellen Speicher. Außerdem wird durch die Verwendung von C/C++-Bibliotheken und Windows-APIs ein Heap in Ihrer Anwendung erstellt, der zumindest einen Teil des virtuellen Speichers reserviert.

Sie könnten z.B. VirtualAlloc verwenden, um einen großen Teil des virtuellen Speichers in einem relativ kleinen Programm zu reservieren, nur um dann festzustellen, dass entweder VirtualAlloc fehlschlägt oder die Anwendung später beim Versuch, eine neue DLL zu laden, fehlschlägt (usw.) Sie können auch nicht immer kontrollieren, welche DLLs geladen werden und wo. Viele A/V- und andere Produkte injizieren DLLs in alle laufenden Prozesse, wenn sie gestartet werden. Wenn dies geschieht, haben diese DLLs oft die erste Wahl bei den Ladeadressen, d. h., die standardmäßig kompilierten/verknüpften DLLs werden wahrscheinlich zugelassen. Wenn eine DLL in der Mitte des 2 GB großen virtuellen Adressraums einer typischen 32-Bit-Windows-Anwendung geladen wird, ist die größte einzelne Zuweisung/Reservierung, die Sie erhalten können, weniger als 1 GB.

Wenn Sie windbg verwenden, können Sie sehen, welche Bereiche des Speichers verbraucht, reserviert usw. sind. Der Befehl lm zeigt Ihnen die Ladeadressen aller DLLs und der EXE und deren Bereich. Der !vadump-Befehl zeigt Ihnen den gesamten vom Prozess verwendeten virtuellen Speicher und die Auslagerungssicherungen an. Die Seitensicherungen sind ein wichtiger Hinweis darauf, was sich dort befindet. In dem folgenden (teilweisen) !vadump eines 64-Bit calc.exe-Prozesses sehen Sie beispielsweise, dass der erste Bereich einfach ein Bereich des virtuellen Speichers ist, der vor dem Zugriff geschützt ist. (Dies hält Sie unter anderem davon ab, Speicher an der Adresse 0 zuzuweisen.) MEM_COMMIT bedeutet, dass der Speicher durch RAM oder die Paging-Datei gesichert ist. PAGE_READWRITE ist möglicherweise Heap-Speicher oder das Datensegment eines geladenen Moduls. PAGE_READEXECUTE ist in der Regel Code, der geladen wurde und in der von lm erstellten Liste auftauchen wird. MEM_RESERVE bedeutet, dass etwas VirtualAlloc aufgerufen hat, um einen Speicherbereich zu reservieren, der aber nicht vom Virtual Memory Manager abgebildet wird, und so weiter...

0:004> !vadump
BaseAddress:       0000000000000000
RegionSize:        0000000000010000
State:             00010000  MEM_FREE
Protect:           00000001  PAGE_NOACCESS

BaseAddress:       0000000000010000
RegionSize:        0000000000010000
State:             00001000  MEM_COMMIT
Protect:           00000004  PAGE_READWRITE
Type:              00040000  MEM_MAPPED

BaseAddress:       0000000000020000
RegionSize:        0000000000003000
State:             00001000  MEM_COMMIT
Protect:           00000002  PAGE_READONLY
Type:              00040000  MEM_MAPPED

Ich hoffe, das hilft, die Dinge zu erklären. Windbg ist ein großartiges Werkzeug und hat viele Erweiterungen, die Ihnen helfen, herauszufinden, wo Speicher verwendet wird.

Wenn Sie sich wirklich nur für den Heap interessieren, schauen Sie sich !heap an.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X