Erzwingt, dass Windows DLLs an Orten lädt, so dass der Speicher minimal fragmentiert ist

8

Meine Anwendung benötigt viel Speicher und große Datenstruktur, um ihre Arbeit auszuführen. Oft benötigt die Anwendung mehr als 1 GB Arbeitsspeicher, und in einigen Fällen müssen meine Kunden die 64-Bit-Version der Anwendung wirklich verwenden, da sie über mehrere Gigabyte Speicher verfügen.

In der Vergangenheit konnte ich dem Benutzer leicht erklären, dass, wenn der Speicher 1,6 bis 1,7 GB Speicherverbrauch erreicht hat, er nicht genügend Speicher hatte oder sehr nah an einer Situation war, in der er nicht genügend Speicher hatte reduzieren Sie ihren Speicher oder wechseln Sie zu einer 64-Bit-Version.

Im letzten Jahr ist mir aufgefallen, dass die Anwendung oft nur etwa 1 GB benutzt, bevor der Speicher nicht mehr ausreicht. Nach einigen Untersuchungen scheint die Speicherfragmentierung die Ursache dieses Problems zu sein. Ich habe VMMAP (ein SysInternals-Dienstprogramm) verwendet, um die Speicherbelegung meiner Anwendung zu überprüfen, und sah etwa Folgendes:

Die orangefarbenen Bereiche sind von meiner Anwendung zugewiesener Speicher. Die violetten Bereiche sind ausführbarer Code.

Wie Sie in der unteren Hälfte des Bildes sehen können, werden die purpurnen Bereiche (die DLLs) unter vielen verschiedenen Adressen geladen, wodurch mein Speicher fragmentiert wird. Dies ist nicht wirklich ein Problem, wenn mein Kunde nicht viele Daten hat, aber wenn mein Kunde Datensätze hat, die mehr als 1 GB benötigen und ein Teil der Anwendung einen großen Speicherblock benötigt (zB 50 MB), Dies kann zu einem Speicherzuordnungsfehler führen, der zum Absturz meiner Anwendung führt.

Die meisten meiner Datenstrukturen sind STL-basiert und erfordern oft keine großen Stücke zusammenhängender Speicher, aber in einigen Fällen (z. B. sehr große Zeichenfolgen) ist es wirklich erforderlich, einen zusammenhängenden Speicherblock zu haben. Leider ist es nicht immer möglich, den Code so zu ändern, dass er einen solchen zusammenhängenden Speicherblock nicht benötigt.

Die Fragen sind:

  • Wie kann ich den Speicherort der DLLs im Speicher beeinflussen, ohne REBASE explizit für alle DLLs auf dem Computer des Kunden zu verwenden oder ohne alle DLLs explizit zu laden.
  • Gibt es eine Möglichkeit, Ladeadressen von DLLs in Ihrer eigenen Anwendungsmanifestdatei anzugeben?
  • Oder gibt es eine Möglichkeit, Windows (über die Manifest-Datei?) anzuweisen, die DLLs nicht zu zerstreuen (ich denke, dass diese Streuung ASLR genannt wird).

Natürlich ist die beste Lösung diejenige, die ich aus der Manifestdatei meiner Anwendung beeinflussen kann, da ich auf das automatische / dynamische Laden von DLLs durch Windows angewiesen bin.

Meine Anwendung ist eine gemischte (verwaltete + nicht verwaltete) Anwendung, obwohl der größte Teil der Anwendung nicht verwaltet wird.

Irgendwelche Vorschläge?

    
Patrick 17.11.2011, 16:15
quelle

3 Antworten

5

Erstens sollte die Fragmentierung des virtuellen Adressraums nicht unbedingt zu einer mangelnden Speicherkapazität führen. Dies wäre der Fall, wenn Ihre Anwendung zusammenhängende Speicherblöcke der entsprechenden Größe zuweisen müsste. Andernfalls sollte der Einfluss der Fragmentierung gering sein.

Sie sagen, die meisten Ihrer Daten sind "STL-basiert", aber wenn Sie zum Beispiel eine riesige std::vector zuweisen, benötigen Sie einen zusammenhängenden Speicherblock.

AFAIK gibt es keine Möglichkeit, die bevorzugte Mapping-Adresse der DLL beim Laden anzugeben. Es gibt also nur zwei Optionen: Entweder rebase es (die DLL-Datei), oder DLL selbst implementieren (was natürlich nicht trivial ist).

Normalerweise müssen Sie die standardmäßigen Windows-API-DLLs nicht erneut bereitstellen, da sie in Ihrem Adressraum sehr eng geladen sind. Fragmentierung kann von einigen DLLs von Drittanbietern (wie Windows-Hooks, Antivirus-Injektionen usw.) kommen.

    
valdo 17.11.2011, 16:37
quelle
3

Sie können dies nicht mit einem Manifest tun, es muss durch die Linker / BASE-Option erfolgen. Linker + Advanced + Basisadresse in der IDE. Am flexibelsten ist es, die Syntax / BASE: @ filename, key zu verwenden, damit der Linker die Basisadresse aus einer Textdatei liest.

Der beste Weg, um die Textdatei zu füllen, ist aus dem Debug + Windows + Modules Fenster. Holen Sie sich den Release-Build Ihres Programms, das im Debugger geladen ist, und laden Sie den gesamten Shebang hoch. Debug + Break All, öffne das Fenster und kopiere es in die Textdatei. Bearbeiten Sie es so, dass es dem erforderlichen Format entspricht, und berechnen Sie die Ladeadressen aus der Spalte Adresse. Lassen Sie genügend Platz zwischen den DLLs, damit Sie nicht ständig die Textdatei optimieren müssen.

    
Hans Passant 17.11.2011 18:39
quelle
1

Wenn Sie in der Lage sind, etwas von Ihrem eigenen Code auszuführen, bevor die fraglichen Bibliotheken geladen werden, könnten Sie sich vorher einen schönen großen Teil des Adressraumes reservieren, um von dort zu reservieren.

Andernfalls müssen Sie die verantwortlichen DLLs identifizieren, um festzustellen, warum sie geladen werden. Sind sie beispielsweise Teil von .NET, der Laufzeitbibliothek der Sprache, Ihres eigenen Codes oder von Drittanbieterbibliotheken, die Sie verwenden?

Für Ihren eigenen Code ist die wahrscheinlich sinnvollste Lösung wahrscheinlich die Verwendung von statischen anstelle von dynamischen Verknüpfungen. Dies sollte auch für die Sprachlaufzeit möglich sein und für Bibliotheken von Drittanbietern möglich sein.

Bei Bibliotheken von Drittanbietern können Sie von implizitem Laden zu explizitem Laden wechseln, so dass das Laden erst erfolgt, nachdem Sie Ihren Adressraum reserviert haben.

Ich weiß nicht, ob Sie irgendetwas an .NET-Bibliotheken tun können; Da jedoch der Großteil Ihres Codes nicht verwaltet wird, ist es möglicherweise möglich, die verwalteten Komponenten zu entfernen, um .NET loszuwerden. Oder vielleicht könnten Sie die .NET-Teile in einen separaten Prozess aufteilen.

    
Harry Johnston 18.11.2011 04:02
quelle

Tags und Links