Was ist Speicherfragmentierung?

203

Ich habe gehört, dass der Begriff "Speicherfragmentierung" im Zusammenhang mit der dynamischen Speicherzuweisung in C ++ einige Male verwendet wurde. Ich habe einige Fragen zum Umgang mit Speicherfragmentierung gefunden, kann aber keine direkte Frage finden, die sich selbst damit befasst. So:

  • Was ist Speicherfragmentierung?
  • Wie kann ich feststellen, ob die Speicherfragmentierung für meine Anwendung ein Problem darstellt? Welche Art von Programm leidet am wahrscheinlichsten?
  • Was sind gute gängige Methoden, um mit Speicherfragmentierung umzugehen?

Ebenfalls:

  • Ich habe gehört, dass die Verwendung dynamischer Zuweisungen die Speicherfragmentierung stark erhöhen kann. Ist das wahr? Im Zusammenhang mit C ++ verstehe ich, dass alle Standardcontainer (std :: string, std :: vector usw.) eine dynamische Speicherzuordnung verwenden. Wenn diese im gesamten Programm verwendet werden (insbesondere std :: string), ist die Speicherfragmentierung eher ein Problem?
  • Wie kann mit Speicherfragmentierung in einer STL-lastigen Anwendung umgegangen werden?
AshleysBrain
quelle
1
Viele tolle Antworten, vielen Dank an alle!
AshleysBrain
4
Es gibt bereits viele gute Antworten, aber hier sind einige Bilder aus einer tatsächlichen Anwendung (Firefox), bei der die Speicherfragmentierung ein großes Problem war: blog.pavlov.net/2007/11/10/memory-fragmentation
Marius Gedminas
2
@MariusGedminas der Link funktioniert nicht mehr, deshalb ist es wichtig, eine kurze Zusammenfassung zusammen mit dem Link bereitzustellen oder die Frage mit einer Zusammenfassung mit dem Link zu beantworten
katta
Sicher, aber es ist über ein halbes Jahrzehnt her
rsethc
3
Unten ist ein aktualisierter Ort für die von Marius geposteten Links: pavlovdotnet.wordpress.com/2007/11/10/memory-fragmentation
TheGameiswar

Antworten:

312

Stellen Sie sich vor, Sie haben eine "große" Fläche (32 Byte) freien Speichers:

----------------------------------
|                                |
----------------------------------

Weisen Sie nun einen Teil davon zu (5 Zuweisungen):

----------------------------------
|aaaabbccccccddeeee              |
----------------------------------

Geben Sie jetzt die ersten vier Zuweisungen frei, aber nicht die fünfte:

----------------------------------
|              eeee              |
----------------------------------

Versuchen Sie nun, 16 Bytes zuzuweisen. Ups, ich kann nicht, obwohl fast doppelt so viel frei ist.

Auf Systemen mit virtuellem Speicher ist die Fragmentierung weniger problematisch als Sie vielleicht denken, da große Zuordnungen nur im virtuellen Adressraum zusammenhängend sein müssen, nicht im physischen Adressraum. Wenn ich in meinem Beispiel einen virtuellen Speicher mit einer Seitengröße von 2 Bytes hätte, könnte ich meine 16-Byte-Zuordnung problemlos vornehmen. Das physische Gedächtnis würde folgendermaßen aussehen:

----------------------------------
|ffffffffffffffeeeeff            |
----------------------------------

Der virtuelle Speicher (der viel größer ist) könnte folgendermaßen aussehen:

------------------------------------------------------...
|              eeeeffffffffffffffff                   
------------------------------------------------------...

Das klassische Symptom der Speicherfragmentierung ist, dass Sie versuchen, einen großen Block zuzuweisen, und dies nicht können, obwohl Sie anscheinend über genügend freien Speicher verfügen. Eine weitere mögliche Konsequenz ist die Unfähigkeit des Prozesses, Speicher an das Betriebssystem zurückzugeben (da in allen vom Betriebssystem zugewiesenen Blöcken noch ein Objekt verwendet wird, obwohl diese Blöcke jetzt größtenteils nicht verwendet werden).

Taktiken zur Verhinderung der Speicherfragmentierung in C ++ funktionieren, indem Objekte aus verschiedenen Bereichen entsprechend ihrer Größe und / oder ihrer erwarteten Lebensdauer zugewiesen werden. Wenn Sie also viele Objekte erstellen und später alle zusammen zerstören möchten, weisen Sie sie aus einem Speicherpool zu. Alle anderen Zuordnungen, die Sie zwischen ihnen vornehmen, stammen nicht aus dem Pool und befinden sich daher nicht zwischen ihnen im Speicher, sodass der Speicher nicht fragmentiert wird.

Im Allgemeinen müssen Sie sich keine großen Sorgen machen, es sei denn, Ihr Programm läuft lange und führt viele Zuweisungen und Freigaben durch. Wenn Sie eine Mischung aus kurzlebigen und langlebigen Objekten haben, sind Sie am meisten gefährdet, aber selbst dann mallocwerden Sie Ihr Bestes tun, um zu helfen. Ignorieren Sie es grundsätzlich, bis Ihr Programm Zuordnungsfehler aufweist oder das System unerwartet zu wenig Arbeitsspeicher hat (fangen Sie dies vorzugsweise beim Testen ab!).

Die Standardbibliotheken sind nicht schlechter als alles andere, was Speicher zuweist, und Standardcontainer verfügen alle über einen AllocVorlagenparameter, mit dem Sie ihre Zuordnungsstrategie bei Bedarf optimieren können.

Steve Jessop
quelle
1
Also ist jedes Zeichen ein Byte? Was würde Ihre "große Fläche" == 32 Bytes machen (ich vermute - hat nicht gezählt) :) Nettes Beispiel, aber die Einheiten vor der letzten Zeile zu erwähnen wäre hilfreich. :)
Jalf
1
@ Jalf: Ja. Ich wollte überhaupt keine Einheiten erwähnen, dann wurde mir am Ende klar, dass ich es musste. Hat daran gearbeitet, während Sie kommentiert haben.
Steve Jessop
Es war ziemlich schwierig, eine "Antwort" zu wählen - viele großartige Antworten hier und ich würde jeden Interessierten ermutigen, alle zu lesen. Trotzdem denke ich, dass Sie hier alle wichtigen Punkte behandelt haben.
AshleysBrain
1
"Die Standardbibliotheken sind nicht schlechter als alles andere, was Speicher zuweist". Das wäre schön, wenn es wahr wäre, aber Implementierungen von Standard-C ++ - Vorlagen wie Zeichenfolge und Vektor können beim Ändern der Größe einige höchst unerwünschte Verhaltensweisen aufweisen. In älteren Versionen von Visual Studio ändert sich die Größe des std :: string im Wesentlichen um 1,5 * current_size (auf die nächsten 8 Bytes). Wenn Sie also weiterhin an eine Zeichenfolge anhängen, können Sie den Heap sehr einfach analysieren, insbesondere auf eingebetteten Systemen. Die beste Verteidigung besteht darin, den erwarteten Platz zu reservieren, um versteckte Neuzuordnungen zu vermeiden.
Locka
1
@ du369: Der virtuelle Speicher ist nicht so stark fragmentiert wie der physische. ffffffffffffffffist eine zusammenhängende Zuordnung im virtuellen Speicher, aber keine solche zusammenhängende Zuordnung kann im physischen Speicher existieren. Wenn Sie es vorziehen, es so zu betrachten, dass es gleich fragmentiert ist, der virtuelle Raum jedoch viel größer ist, können Sie es stattdessen so betrachten. Der wichtige praktische Punkt ist, dass die Verwendung großer virtueller Adressräume oft ausreicht, um die Fragmentierung zu ignorieren. Daher ist es hilfreich, wenn ich meine 16-Byte-Zuweisung vornehmen kann.
Steve Jessop
73

Was ist Speicherfragmentierung?

Bei der Speicherfragmentierung wird der größte Teil Ihres Speichers in einer großen Anzahl nicht zusammenhängender Blöcke oder Blöcke zugewiesen. Ein guter Prozentsatz Ihres gesamten Speichers wird nicht zugewiesen, ist jedoch für die meisten typischen Szenarien unbrauchbar. Dies führt zu Ausnahmen aufgrund von Speichermangel oder Zuordnungsfehlern (dh malloc gibt null zurück).

Der einfachste Weg, darüber nachzudenken, besteht darin, sich vorzustellen, dass Sie eine große leere Wand haben, auf die Sie Bilder unterschiedlicher Größe setzen müssen . Jedes Bild nimmt eine bestimmte Größe an und Sie können es offensichtlich nicht in kleinere Teile aufteilen, um es passend zu machen. Sie benötigen eine leere Stelle an der Wand, die Größe des Bildes, oder Sie können es nicht aufstellen. Wenn Sie jetzt anfangen, Bilder an die Wand zu hängen, und Sie nicht genau wissen, wie Sie sie anordnen, werden Sie bald eine Wand haben, die teilweise mit Bildern bedeckt ist, und obwohl Sie möglicherweise leere Stellen haben, passen die meisten neuen Bilder nicht weil sie größer sind als die verfügbaren Plätze. Sie können immer noch sehr kleine Bilder aufhängen, aber die meisten passen nicht. Sie müssen also die bereits an der Wand befindlichen neu anordnen (kompaktieren), um Platz für mehr zu schaffen.

Stellen Sie sich nun vor, die Wand ist Ihr (Haufen-) Gedächtnis und die Bilder sind Objekte. Das ist Speicherfragmentierung.

Wie kann ich feststellen, ob die Speicherfragmentierung für meine Anwendung ein Problem darstellt? Welche Art von Programm leidet am wahrscheinlichsten?

Ein verräterisches Zeichen dafür, dass Sie möglicherweise mit Speicherfragmentierung zu tun haben, ist, wenn Sie viele Zuordnungsfehler erhalten, insbesondere wenn der Prozentsatz des verwendeten Speichers hoch ist - aber noch nicht der gesamte Speicher belegt ist -, also sollten Sie technisch gesehen genügend Platz haben für die Objekte, die Sie zuordnen möchten.

Wenn der Speicher stark fragmentiert ist, dauert die Speicherzuweisung wahrscheinlich länger, da der Speicherzuweiser mehr Arbeit leisten muss, um einen geeigneten Speicherplatz für das neue Objekt zu finden. Wenn Sie wiederum viele Speicherzuordnungen haben (was Sie wahrscheinlich seit der Speicherfragmentierung tun), kann die Zuweisungszeit sogar zu merklichen Verzögerungen führen.

Was sind gute gängige Methoden, um mit Speicherfragmentierung umzugehen?

Verwenden Sie einen guten Algorithmus zum Zuweisen von Speicher. Anstatt Speicher für viele kleine Objekte zuzuweisen, weisen Sie Speicher für ein zusammenhängendes Array dieser kleineren Objekte vorab zu. Wenn Sie beim Zuweisen von Speicher etwas verschwenderisch vorgehen, kann dies manchmal zu Leistungseinbußen führen und Ihnen die Mühe ersparen, sich mit der Speicherfragmentierung befassen zu müssen.

Mike Dinescu
quelle
10
+1. Ich habe gerade meine vorgeschlagene Antwort gelöscht, weil Ihre Metapher "Bilder an der Wand" wirklich, wirklich gut und klar ist.
ctacke
Ich würde es mehr mögen, wenn Sie die Tatsache betonen würden, dass die Bilder unterschiedliche Größen haben müssen. Andernfalls erfolgt keine Fragmentierung.
Björn Pollex
1
Interessanterweise werden Hauptspeicherdatenbanken heutzutage etwas praktischer (wobei wirklich viel Speicher verfügbar ist). In diesem Zusammenhang ist anzumerken, dass das Lesen von fortlaufenden Zeilen aus dem RAM bei Festplatten viel schneller ist als bei fragmentierten Daten.
Björn Pollex
1
Schöne visuelle Analogie zu den Bildern an den Wänden, aber der Hauptspeicher ist nicht zweidimensional! Trotzdem schöne Antwort, danke.
AshleysBrain
24

Die Speicherfragmentierung ist das gleiche Konzept wie die Festplattenfragmentierung: Sie bezieht sich auf die Verschwendung von Speicherplatz, da die verwendeten Bereiche nicht eng genug zusammengepackt sind.

Angenommen, Sie haben für ein einfaches Spielzeugbeispiel zehn Byte Speicher:

 |   |   |   |   |   |   |   |   |   |   |
   0   1   2   3   4   5   6   7   8   9

Ordnen wir nun drei Drei-Byte-Blöcke mit den Namen A, B und C zu:

 | A | A | A | B | B | B | C | C | C |   |
   0   1   2   3   4   5   6   7   8   9

Geben Sie nun Block B frei:

 | A | A | A |   |   |   | C | C | C |   |
   0   1   2   3   4   5   6   7   8   9

Was passiert nun, wenn wir versuchen, einen Vier-Byte-Block D zuzuweisen? Nun, wir haben vier Bytes Speicher frei, aber wir haben nicht vier zusammenhängende Bytes Speicher frei, also können wir D nicht zuweisen! Dies ist eine ineffiziente Speichernutzung, da wir D hätten speichern können, dies aber nicht konnten. Und wir können C nicht verschieben, um Platz zu schaffen, da sehr wahrscheinlich einige Variablen in unserem Programm auf C zeigen und wir nicht alle diese Werte automatisch finden und ändern können.

Woher weißt du, dass es ein Problem ist? Das größte Zeichen ist, dass der virtuelle Speicher Ihres Programms erheblich größer ist als der tatsächlich verwendete Speicher. In einem Beispiel aus der Praxis hätten Sie viel mehr als zehn Bytes Speicher, sodass D erst ab Byte 9 zugewiesen würde und die Bytes 3-5 nicht verwendet würden, es sei denn, Sie hätten später etwas mit einer Länge von drei Bytes oder weniger zugewiesen.

In diesem Beispiel sind 3 Bytes nicht viel zu verschwenden. Betrachten Sie jedoch einen pathologischeren Fall, in dem zwei Zuordnungen von ein paar Bytes beispielsweise zehn Megabyte voneinander entfernt sind und Sie einen Block mit einer Größe von 10 Megabyte zuweisen müssen + 1 Byte. Sie müssen das Betriebssystem um mehr als zehn Megabyte mehr virtuellen Speicher bitten, um dies zu tun, obwohl Sie nur ein Byte davon entfernt sind, bereits genügend Speicherplatz zu haben.

Wie verhindern Sie das? Die schlimmsten Fälle treten in der Regel auf, wenn Sie häufig kleine Objekte erstellen und zerstören, da dies zu einem "Schweizer Käse" -Effekt mit vielen kleinen Objekten führt, die durch viele kleine Löcher getrennt sind, sodass keine größeren Objekte in diesen Löchern zugeordnet werden können. Wenn Sie wissen, dass Sie dies tun werden, besteht eine effektive Strategie darin, einen großen Speicherblock als Pool für Ihre kleinen Objekte vorab zuzuweisen und dann die Erstellung der kleinen Objekte in diesem Block manuell zu verwalten, anstatt sie zuzulassen Der Standard-Allokator behandelt dies.

Im Allgemeinen ist es weniger wahrscheinlich, dass der Speicher fragmentiert wird, je weniger Zuweisungen Sie vornehmen. STL geht damit jedoch ziemlich effektiv um. Wenn Sie eine Zeichenfolge haben, die die gesamte aktuelle Zuordnung verwendet und ein Zeichen an sie anfügt, wird sie nicht einfach ihrer aktuellen Länge plus eins neu zugewiesen, sondern ihre Länge verdoppelt . Dies ist eine Variation der Strategie "Pool für häufige kleine Allokationen". Die Zeichenfolge beansprucht einen großen Teil des Speichers, so dass sie mit wiederholten kleinen Vergrößerungen effizient umgehen kann, ohne wiederholte kleine Neuzuweisungen vorzunehmen. Tatsächlich machen alle STL-Container so etwas, sodass Sie sich im Allgemeinen nicht zu viele Gedanken über die Fragmentierung machen müssen, die durch die automatische Neuzuweisung von STL-Containern verursacht wird.

Obwohl natürlich STL Umhüllungen Pool nicht Speicher zwischen einander, so dass , wenn Sie viele kleine Behälter (eher als ein paar Container , die häufig der Größe verändert werden) erstellen Sie gehen zu Sorge haben , sich mit der Fragmentierung in der gleichen Art und Weise verhindern Sie würde für alle häufig erstellten kleinen Objekte, STL oder nicht.

Tyler McHenry
quelle
14
  • Was ist Speicherfragmentierung?

Speicherfragmentierung ist das Problem, dass Speicher unbrauchbar wird, obwohl er theoretisch verfügbar ist. Es gibt zwei Arten der Fragmentierung: die interne Fragmentierung ist Speicher, der zugewiesen wird, aber nicht verwendet werden kann (z. B. wenn Speicher in 8-Byte-Blöcken zugewiesen wird, das Programm jedoch wiederholt einzelne Zuweisungen ausführt, wenn es nur 4 Byte benötigt). Die externe Fragmentierung ist das Problem, dass der freie Speicher in viele kleine Blöcke aufgeteilt wird, so dass große Zuordnungsanforderungen nicht erfüllt werden können, obwohl insgesamt genügend freier Speicher vorhanden ist.

  • Wie kann ich feststellen, ob die Speicherfragmentierung für meine Anwendung ein Problem darstellt? Welche Art von Programm leidet am wahrscheinlichsten?

Speicherfragmentierung ist ein Problem, wenn Ihr Programm viel mehr Systemspeicher verwendet, als die tatsächlichen Paylod-Daten erfordern würden (und Sie Speicherlecks ausgeschlossen haben).

  • Was sind gute gängige Methoden, um mit Speicherfragmentierung umzugehen?

Verwenden Sie einen guten Speicherzuweiser. IIRC, diejenigen, die eine "Best-Fit" -Strategie verwenden, sind im Allgemeinen viel besser darin, Fragmentierung zu vermeiden, wenn auch etwas langsamer. Es wurde jedoch auch gezeigt, dass es für jede Allokationsstrategie pathologische Worst-Cases gibt. Glücklicherweise sind die typischen Zuordnungsmuster der meisten Anwendungen für die Zuweiser relativ harmlos. Es gibt eine Reihe von Artikeln, wenn Sie an den Details interessiert sind:

  • Paul R. Wilson, Mark S. Johnstone, Michael Neely und David Boles. Dynamische Speicherzuordnung: Eine Umfrage und kritische Überprüfung. In Proceedings of the 1995 International Workshop on Memory Management, Springer Verlag LNCS, 1995
  • Mark S. Johnstone, Paul R. Wilson. Das Problem der Speicherfragmentierung: Gelöst? In ACM SIG-PLAN Notices, Band 34 Nr. 3, Seiten 26-36, 1999
  • MR Garey, RL Graham und JD Ullman. Worst-Case-Analyse von Speicherzuweisungsalgorithmen. Im vierten jährlichen ACM-Symposium zur Theorie des Rechnens, 1972
Michael Borgwardt
quelle
9

Update:
Google TCMalloc: Thread-Caching Malloc
Es wurde festgestellt, dass es in einem lang laufenden Prozess recht gut mit Fragmentierung umgehen kann.


Ich habe eine Serveranwendung entwickelt, die Probleme mit der Speicherfragmentierung unter HP-UX 11.23 / 11.31 ia64 hatte.

Es sah so aus. Es gab einen Prozess, bei dem Speicherzuweisungen und Freigaben vorgenommen und tagelang ausgeführt wurden. Und obwohl es keine Speicherlecks gab, stieg der Speicherverbrauch des Prozesses weiter an.

Über meine Erfahrung. Unter HP-UX ist es sehr einfach, Speicherfragmentierungen mit HP-UX gdb zu finden. Sie legen einen Haltepunkt fest und führen diesen Befehl aus, wenn Sie ihn drücken: info heapund sehen alle Speicherzuordnungen für den Prozess und die Gesamtgröße des Heapspeichers. Dann setzen Sie Ihr Programm fort und einige Zeit später erreichen Sie erneut den Haltepunkt. Du machst es wiederinfo heap . Wenn die Gesamtgröße des Heapspeichers größer ist, aber die Anzahl und die Größe der separaten Zuordnungen gleich sind, haben Sie wahrscheinlich Probleme mit der Speicherzuweisung. Falls erforderlich, überprüfen Sie dies einige Male.

Mein Weg, die Situation zu verbessern, war dieser. Nachdem ich einige Analysen mit HP-UX gdb durchgeführt hatte, stellte ich fest, dass Speicherprobleme durch die Tatsache verursacht wurden, dass ich std::vectoreinige Arten von Informationen aus einer Datenbank gespeichert habe. std::vectorerfordert, dass seine Daten in einem Block gehalten werden müssen. Ich hatte ein paar Container basierend auf std::vector. Diese Container wurden regelmäßig neu erstellt. Es gab häufig Situationen, in denen neue Datensätze zur Datenbank hinzugefügt und anschließend die Container neu erstellt wurden. Und da die neu erstellten Container größer waren, passten sie nicht in verfügbare Blöcke mit freiem Speicher, und die Laufzeit forderte einen neuen größeren Block vom Betriebssystem an. Infolgedessen stieg der Speicherverbrauch des Prozesses, obwohl keine Speicherlecks auftraten. Ich habe die Situation verbessert, als ich die Container gewechselt habe. Anstatt std::vectorich anfing zu benutzenstd::deque Dies hat eine andere Art der Zuweisung von Speicher für Daten.

Ich weiß, dass eine Möglichkeit, eine Speicherfragmentierung unter HP-UX zu vermeiden, darin besteht, entweder Small Block Allocator oder MallocNextGen zu verwenden. Unter RedHat Linux scheint der Standardzuweiser die Zuweisung vieler kleiner Blöcke ziemlich gut zu handhaben. Unter Windows gibt es Low-fragmentation Heapund es behebt das Problem einer großen Anzahl kleiner Zuordnungen.

Mein Verständnis ist, dass Sie in einer STL-lastigen Anwendung zuerst Probleme identifizieren müssen. Speicherzuordnungen (wie in libc) behandeln tatsächlich das Problem vieler kleiner Zuordnungen, was typisch ist std::string(zum Beispiel gibt es in meiner Serveranwendung viele STL-Zeichenfolgen, aber wie ich beim Ausführen sehe, info heapverursachen sie keine Probleme). Mein Eindruck ist, dass Sie häufige große Zuweisungen vermeiden müssen. Leider gibt es Situationen, in denen Sie sie nicht vermeiden können und Ihren Code ändern müssen. Wie ich in meinem Fall sagte, habe ich die Situation beim Umstieg verbessert std::deque. Wenn Sie Ihre Speicherfragmentierung identifizieren, können Sie möglicherweise genauer darüber sprechen.


quelle
6

Eine Speicherfragmentierung tritt am wahrscheinlichsten auf, wenn Sie viele Objekte unterschiedlicher Größe zuweisen und freigeben . Angenommen, Sie haben das folgende Layout im Speicher:

obj1 (10kb) | obj2(20kb) | obj3(5kb) | unused space (100kb)

Wenn obj2es freigegeben wird, verfügen Sie über 120 KB nicht verwendeten Speicher, können jedoch keinen vollständigen Block mit 120 KB zuweisen, da der Speicher fragmentiert ist.

Zu den gängigen Techniken zur Vermeidung dieses Effekts gehören Ringpuffer und Objektpools . Im Kontext der STL können Methoden wie std::vector::reserve()helfen.

Björn Pollex
quelle
3

Was ist Speicherfragmentierung?

Wenn Ihre App dynamischen Speicher verwendet, weist sie Speicherblöcke zu und gibt sie frei. Am Anfang ist der gesamte Speicherplatz Ihrer App ein zusammenhängender Block freien Speichers. Wenn Sie jedoch Blöcke unterschiedlicher Größe zuweisen und freigeben, wird der Speicher fragmentiert , dh anstelle eines großen zusammenhängenden freien Blocks und einer Anzahl zusammenhängender zugeordneter Blöcke werden zugewiesene und freie Blöcke verwechselt. Da die freien Blöcke eine begrenzte Größe haben, ist es schwierig, sie wiederzuverwenden. Beispielsweise haben Sie möglicherweise 1000 Byte freien Speicher, können jedoch keinen Speicher für einen 100-Byte-Block zuweisen, da alle freien Blöcke höchstens 50 Byte lang sind.

Eine andere, unvermeidbare, aber weniger problematische Fragmentierungsquelle besteht darin, dass in den meisten Architekturen Speicheradressen an Byte-Grenzen von 2, 4, 8 usw. ausgerichtet sein müssen (dh die Adressen müssen Vielfache von 2, 4, 8 usw. sein). Dies bedeutet, dass Selbst wenn Sie z. B. eine Struktur mit 3 charFeldern haben, kann Ihre Struktur eine Größe von 12 anstelle von 3 haben, da jedes Feld an einer 4-Byte-Grenze ausgerichtet ist.

Wie kann ich feststellen, ob die Speicherfragmentierung für meine Anwendung ein Problem darstellt? Welche Art von Programm leidet am wahrscheinlichsten?

Die offensichtliche Antwort ist, dass Sie eine Ausnahme wegen Speichermangels erhalten.

Anscheinend gibt es keine gute tragbare Möglichkeit, Speicherfragmentierung in C ++ - Apps zu erkennen. Siehe diese Antwort für weitere Details.

Was sind gute gängige Methoden, um mit Speicherfragmentierung umzugehen?

In C ++ ist dies schwierig, da Sie direkte Speicheradressen in Zeigern verwenden und keine Kontrolle darüber haben, wer auf eine bestimmte Speicheradresse verweist. Das Neuanordnen der zugewiesenen Speicherblöcke (wie es der Java-Garbage-Collector tut) ist daher keine Option.

Ein benutzerdefinierter Zuweiser kann helfen, indem er die Zuordnung kleiner Objekte in einem größeren Speicherblock verwaltet und die freien Steckplätze in diesem Block wiederverwendet.

Péter Török
quelle
3

Dies ist eine super vereinfachte Version für Dummies.

Wenn Objekte im Speicher erstellt werden, werden sie am Ende des verwendeten Teils im Speicher hinzugefügt.

Wenn ein Objekt gelöscht wird, das sich nicht am Ende des verwendeten Speicherbereichs befindet, dh dieses Objekt zwischen zwei anderen Objekten liegt, wird ein "Loch" erstellt.

Dies nennt man Fragmentierung.

user455288
quelle
2

Wenn Sie ein Element auf dem Heap hinzufügen möchten, muss der Computer nach Speicherplatz suchen, der zu diesem Element passt. Aus diesem Grund können dynamische Zuweisungen, wenn sie nicht in einem Speicherpool oder mit einem gepoolten Zuweiser durchgeführt werden, die Dinge "verlangsamen". Für eine schwere STL-Anwendung, wenn Sie Multithreading ausführen, gibt es den Hoard Allocator oder die TBB Intel- Version.

Wenn der Speicher fragmentiert ist, können zwei Dinge auftreten:

  1. Es muss mehr gesucht werden, um einen guten Platz zum Aufkleben "großer" Objekte zu finden. Das heißt, mit vielen kleinen Objekten, die verstreut sind, um einen schönen zusammenhängenden Speicherblock zu finden, kann es unter bestimmten Bedingungen schwierig sein (diese sind extrem).
  2. Der Speicher ist keine leicht lesbare Einheit. Prozessoren sind darauf beschränkt, wie viel sie wo halten können. Dazu tauschen sie Seiten aus, wenn sich ein Element an einer Stelle befindet, die aktuellen Adressen jedoch an einer anderen. Wenn Sie ständig Seiten austauschen müssen, kann sich die Verarbeitung verlangsamen (wiederum extreme Szenarien, in denen dies die Leistung beeinträchtigt). Weitere Informationen finden Sie in diesem Beitrag zum virtuellen Speicher .
Wheaties
quelle
1

Speicherfragmentierung tritt auf, weil Speicherblöcke unterschiedlicher Größe angefordert werden. Betrachten Sie einen Puffer von 100 Bytes. Sie fordern zwei Zeichen und dann eine Ganzzahl an. Jetzt geben Sie die beiden Zeichen frei und fordern eine neue Ganzzahl an. Diese Ganzzahl kann jedoch nicht in das Leerzeichen der beiden Zeichen passen. Dieser Speicher kann nicht wiederverwendet werden, da er sich nicht in einem zusammenhängenden Block befindet, der groß genug ist, um ihn neu zuzuweisen. Darüber hinaus haben Sie viel Allokator-Overhead für Ihre Zeichen in Anspruch genommen.

Im Wesentlichen wird der Speicher auf den meisten Systemen nur in Blöcken einer bestimmten Größe geliefert. Sobald Sie diese Blöcke aufgeteilt haben, können sie erst wieder zusammengefügt werden, wenn der gesamte Block freigegeben ist. Dies kann dazu führen, dass ganze Blöcke verwendet werden, wenn tatsächlich nur ein kleiner Teil des Blocks verwendet wird.

Der primäre Weg, um die Heap-Fragmentierung zu reduzieren, besteht darin, größere, weniger häufige Zuweisungen vorzunehmen. Im Extremfall können Sie einen verwalteten Heap verwenden, der Objekte zumindest innerhalb Ihres eigenen Codes verschieben kann. Damit ist das Problem vollständig beseitigt - jedenfalls aus Speichersicht. Offensichtlich hat das Bewegen von Objekten und dergleichen Kosten. In Wirklichkeit haben Sie nur dann wirklich ein Problem, wenn Sie häufig sehr kleine Mengen vom Haufen zuweisen. Die Verwendung zusammenhängender Container (Vektor, Zeichenfolge usw.) und die Zuordnung auf dem Stapel so weit wie möglich (immer eine gute Idee für die Leistung) ist der beste Weg, um diese zu reduzieren. Dies erhöht auch die Cache-Kohärenz, wodurch Ihre Anwendung schneller ausgeführt wird.

Was Sie beachten sollten, ist, dass Sie auf einem 32-Bit-x86-Desktopsystem über insgesamt 2 GB Speicher verfügen, der in 4-KB- "Seiten" aufgeteilt ist (ziemlich sicher, dass die Seitengröße auf allen x86-Systemen gleich ist). Sie müssen eine omgwtfbbq-Fragmentierung aufrufen, um ein Problem zu haben. Fragmentierung gehört wirklich der Vergangenheit an, da moderne Heaps für die überwiegende Mehrheit der Anwendungen übermäßig groß sind und es eine Vielzahl von Systemen gibt, die dies aushalten können, z. B. verwaltete Heaps.

Hündchen
quelle
0

Welche Art von Programm leidet am wahrscheinlichsten?

Ein schönes (= schreckliches) Beispiel für die Probleme im Zusammenhang mit der Speicherfragmentierung war die Entwicklung und Veröffentlichung von "Elemental: War of Magic" , einem Computerspiel von Stardock .

Das Spiel wurde für 32 Bit / 2 GB Speicher entwickelt und musste viele Optimierungen in der Speicherverwaltung vornehmen, damit das Spiel innerhalb dieser 2 GB Speicher funktioniert. Da die "Optimierung" zu einer konstanten Zuweisung und Aufhebung der Zuweisung führte, trat im Laufe der Zeit eine Fragmentierung des Heapspeichers auf und das Spiel stürzte jedes Mal ab .

Auf YouTube gibt es ein "Kriegsgeschichten" -Interview .

Thomas
quelle