(Wie) berücksichtigen Sie die Speicherfragmentierung?

8

Ich verwende ein Beispiel aus der Finite-Elemente-Theorie, aber jeder, der eine große Datenstruktur unterhält und diese sukzessive erweitert, wird etwas Ähnliches finden.

Angenommen, ich habe ein unstrukturiertes Netz aus Punkten und Dreiecken, wobei die Punkte durch Koordinaten (z. B. und ) gegeben sind und die Dreiecke jeweils aus drei Punktindizes (z. B. , und ) bestehen.y i j kxyichjk

Wie in FEM üblich, wird das Netz nacheinander verfeinert. Wenn wir auf eine globale regelmäßige Verfeinerung zurückgreifen, wächst die Anzahl der Dreiecke mit jeder Iteration der Verfeinerung um den Faktor . Je nachdem, wie dies gemacht wird, entwickelt sich das Speicherlayout unterschiedlich.4

Angenommen, das Netz belegt die Speicherzellen 1 bis 300, alles darüber hinaus ist frei.

Beispiel 1:

Wir weisen den Platz für das neue Netz, die Zellen 301 bis 1501, zu, füllen es mit den Daten des verfeinerten Netzes und vergessen das alte. Das nächste verfeinerte Netz wird in den Zellen 1501 bis 6300 platziert, das nächste in den Zellen 6301 bis 21500 und so weiter. Die Position des aktuellen Netzes wird im Speicher "nach rechts" verschoben, während ein riesiger Patch nicht verwendet wird. Möglicherweise geht uns vorzeitig der Speicher aus.

Man könnte im obigen Beispiel beobachten, dass dies uns nur für einen Verfeinerungsschritt behindert, da uns auch ohne diese Fragmentierung eine Verfeinerung später der gesamte Speicher ausgehen würde. Wenn auch das Scheitelpunktarray berücksichtigt wird, kann das Problem schwerwiegender werden.

Wie kann dies umgangen werden?

Beispiel 2:

Ordnen Sie das Dreiecksarray den Zellen 1..1200 neu zu. Erstellen Sie das neue Netz in den Zellen 1201 bis 2400. Kopieren Sie den Inhalt dieser Arbeitskopie in die Zellen 1..1200 und vergessen Sie die Arbeitskopie. Ähnlich wiederholen.

Ok, wir haben immer noch vorzeitig keinen Speicher mehr, weil wir eine Arbeitskopie benötigen. Wie wäre es damit:

Beispiel 3:

Ordnen Sie das Dreiecksarray den Zellen 1..1500 neu zu. Kopieren Sie das alte Netz nach 1201 .. 1500. Erstellen Sie ein neues Netz in den Zellen 1..1200. Dann vergessen Sie die Kopie des alten Netzes.

Der Fall hier ist künstlich, weil man auf diesen Skalen keine globale Netzverfeinerung verwenden würde. Wenn das Wachstum viel kleiner ist, ist eine Neuausrichtung des Speichers möglich, um eine Fragmentierung zu vermeiden. Jedoch,

Fragen:

  1. Wird die Speicherfragmentierung im praktischen wissenschaftlichen Rechnen / Hochleistungsrechnen jemals kritisch?

  2. Wenn überhaupt, wie vermeidest du das? Vielleicht ist mein Maschinenmodell sogar falsch, und das Betriebssystem richtet den Speicher durch starke Magie stillschweigend neu aus oder verwaltet fragmentierte Blöcke auf dem Heap.

  3. Genauer gesagt, wie wirkt es sich auf das Netzmanagement aus?

Shuhalo
quelle

Antworten:

11

Ich bin mit Matt nicht einverstanden, was die vollständige Speichernutzung für das Netz und die zusätzliche Indirektion betrifft. Bei expliziten Methoden ist es üblich, dass eine sehr kleine Anzahl von Vektoren (z. B. 2) den gesamten Simulationszustand darstellt. Bei einem Skalarproblem kann das Definieren der Koordinaten des Netzes mehr als dies sein, und wenn die Konnektivität explizit ist, ist es wesentlich mehr. Hochauflösende Simulationen mit expliziten Methoden erfordern häufig eine große Anzahl von Zeitschritten. Daher werden häufig relativ kleine Subdomänen verwendet, um einen Modelllauf in einer angemessenen Wandzeit durchführen zu können. Daher ist es aufgrund der Kosten für das Speichern des Netzes nicht allzu häufig, dass der Speicher knapp wird.

Ein weitaus grundlegenderes Problem ist die Speicherbandbreitenanforderung für jeden Schritt eines Algorithmus (z. B. Auswerten eines Residuums oder Ausführen eines Zeitschritts). Bei unstrukturierten Methoden umfasst dies einige netzbasierte Informationen, erfordert jedoch normalerweise nicht den Zugriff auf den gesamten Netzspeicher. Beachten Sie, dass selbst wenn der Solver-Speicher den Mesh-Speicher dominiert (wie dies bei vielen impliziten Methoden üblich ist), die Simulation häufig von den Bandbreitenanforderungen einer Mesh-Durchquerung abhängt. Es gibt zwei Faktoren:

Welche Daten werden während der routinemäßigen Netzdurchquerung benötigt?

Bei Finite-Elemente-Methoden wird eine Zuordnung von Element zu Global benötigt. Bei höheren als der ersten Ordnung wird dies häufig implementiert, indem Freiheitsgrade mit Zwischenelementen wie Flächen und Kanten verknüpft werden. Sobald jedoch die Zuordnung von Element zu Global erstellt wurde, werden die Zwischenelemente nicht mehr benötigt. Insbesondere wird die Konnektivität der Zwischeneinheiten niemals direkt von einer Simulation verwendet. Diese Informationen werden möglicherweise für viele Iterationen eines impliziten Lösers oder Zeitintegrators nicht berührt, sind jedoch während der Adaptivität oder zum Einrichten eines neuen Funktionsraums erforderlich. Es ist üblich, die Element-zu-Global-Zuordnung in einem einfachen Array einzurichten und die "Netz" -Datenstruktur während dieses Zeitraums nicht zu berühren. In diesem Fall sind das Speicherformat und die Datenlokalität des Netzes selbst weniger wichtig.

Methoden mit endlichem Volumen erfordern das Zellvolumen, die Konnektivität von Angesicht zu Zelle, den Gesichtsbereich und die Gesichtsnormalen. Beachten Sie, dass Scheitelpunktkoordinaten oder Konnektivität nicht verfügbar sein müssen. In extremen Beispielen (z. B. FUN3D) werden alle anderen Informationen während der Offline-Vorverarbeitung (Netzgenerierung) verworfen, und nur diese reduzierte Darstellung steht der Simulation zur Verfügung. Dies ist sehr effizient, schließt jedoch das Verschieben von Netzen und die adaptive Verfeinerung aus.

Wie sind diese Daten im Speicher angeordnet?

Bei vielen Simulationen mit bescheidener Genauigkeit und Komplexität der Physik ist die Leistung durch die Speicherbandbreite begrenzt. Moderne CPU-Architekturen von IBM, Intel, AMD und NVidia unterstützen eine arithmetische Intensität zwischen 4 und 8 Flops / Byte. Mit solch effizienten Gleitkommaeinheiten sollten wir versuchen, unsere Algorithmen für die Speicherbandbreite zu optimieren. Dies könnte etwas tiefgreifende algorithmische Änderungen beinhalten, wie die Bevorzugung nicht zusammengesetzter Methoden hoher Ordnung (vergleiche die "zusammengesetzten" und (nicht zusammengebauten) "Tensor" -Linien in der zweiten Figur), aber wir können zunächst versuchen, die von der Hardware bereitgestellte Bandbreite vollständig zu nutzen. Dies beinhaltet häufig die Berechnung der Reihenfolge (von Scheitelpunkten, Flächen, Zellen usw.), sodass der Cache so gut wie möglich wiederverwendet wird. Dazu gehört auch das Ausnutzen des Blockierens und Ordnens von Unbekannten, um minimale Metadaten zu erhalten und die richtige Anzahl von Datenströmen zu aktivieren. (Normalerweise führt eine unstrukturierte 3D-Simulation zu "zu vielen" Streams, aber einige moderne Hardware wie POWER7 benötigt viele Streams, um die Bandbreite zu sättigen. Daher ist es gelegentlich sinnvoll, Daten absichtlich zu organisieren, um mehr Streams zu aktivieren.) Die PETSc-FUN3D-Arbeit bietet eine klassische , aber immer noch hochrelevante Diskussion dieser Leistungsoptimierung für unstrukturierte implizite CFD.

Vorschläge für Ihr Problem

  1. Hab keine Angst vor malloc(). Es ist nicht erforderlich, die aktuelle Verfeinerung des Netzes in dasselbe Array wie den gröberen inaktiven Teil des Netzes zu packen. Wann immer Sie keinen alten Teil des Netzes mehr benötigen, nur free()diesen.

  2. Berechnen Sie nach der Verfeinerung eine gute Reihenfolge für diese Verfeinerungsstufe ( Reverse Cuthill-McKee ist beliebt, es können jedoch auch mehr Cache-spezifische Ordnungen verwendet werden). Wenn Ihr Lastungleichgewicht angemessen ist (z. B. weniger als 10%), können Sie diese neue Reihenfolge lokal berechnen (ohne parallele Partitionierung und Umverteilung). Die Kosten sind wahrscheinlich ähnlich wie bei einer einzelnen "Physik" -Netzdurchquerung, können diese Durchquerungen jedoch um den Faktor 2 oder mehr beschleunigen. Dieser Schritt zahlt sich normalerweise immer dann aus, wenn die Netzadaptivität nicht bei jedem Schritt auftritt"Physik" -Netzdurchquerung. Wenn für Ihr Problem eine so häufige Anpassungsfähigkeit erforderlich ist, nehmen Sie wahrscheinlich kleine Änderungen vor und sollten gelegentlich noch nachbestellen. Ich würde immer noch feinkörnige Speicherpools vermeiden, da dies die Neuordnung erschwert. Sie können jedoch relativ große Blöcke verwenden, um die maximale Speichernutzung, die Bestellkosten und die Kosten für inkrementelle Updates auszugleichen.

  3. Abhängig von der Diskretisierung sollten Sie eine reduzierte Darstellung mit geringen Speicherbandbreitenanforderungen für die "Physik" -Durchquerungen extrahieren. Unnötige Indirektion ist schlecht, da sie die Bandbreitenanforderungen erhöht und, wenn das Ziel der Indirektion unregelmäßig ist, eine schlechte Wiederverwendung des Caches verursacht und das Prefetch verhindert.

Jed Brown
quelle
Dieses Problem über die Zwischenkonnektivität, die Bandbreite beansprucht, ist für Ihren Fall trivial zu beseitigen und wird durch PETSc DMComplex behoben. Sie trennen die Speicherung der Maschentopologie von Felddaten. Das Speichern von element-to-global ist in jedem Fall falsch, denke ich. Die Topologie hat die gleichen Informationen auf kleinerem Raum und ist viel umfangreicher. Sie erstellen eine Durchquerung wie eine Schließung mit Offsets im Speicher.
Matt Knepley
5

In Deal.II verfeinern wir das Netz, indem wir alte Zellen wegwerfen und durch neue ersetzen. Es werden aber auch neue in die Speicherlöcher gelegt, die gelöschte Zellen hinterlassen. Alle Schleifen über alle Zellen werden dann in der Reihenfolge ausgeführt, in der Zellen im Speicher angetroffen werden, um die Cache-Treffer hoch zu halten.

Die größere Frage ist, wie Sie die Daten speichern, die Zellen definieren. Sie können natürlich auch Simplex {Vertex vertices [4]; int material_id; int subdomain_id; Bool verwendet; void * user_data; };

Klassentriangulation {Simplex * -Zellen; }; Dies ist jedoch nicht cacheeffizient, da die meisten Schleifen über alle Zellen nur eine Teilmenge der Daten berühren, die Sie in Ihrer Simplex-Datenstruktur speichern, und daher nur ein Bruchteil der Daten verwendet wird, die im Cache landen. Eine bessere Strategie besteht darin, Folgendes zu tun: class Triangulation {Vertex * vertices; int * material_ids; int * subdomain_ids; bool * used_flags; void * * user_data; }; Da in Schleifen über alle Zellen nachfolgende Iterationen wahrscheinlich auf dieselbe Teilmenge von Daten zugreifen, die Zellen definieren, laden Read-Ahead-Caches nur die Daten vor, die Sie tatsächlich verwenden werden, und führen folglich zu hohen Cache-Trefferquoten.

Wolfgang Bangerth
quelle
4

1) Nein. Der Solver-Speicher überwiegt bei weitem den Mesh-Speicher. Selbst wenn Sie den leichtesten, explizitesten Löser ausführen, macht das Netz höchstens 25% des Speichers der Simulation aus und viel wahrscheinlicher <10%.

2) Teilen Sie Ihre Zuordnung auf und verwenden Sie Speicherpooling. Sie müssen nicht für das gesamte Netz einen zusammenhängenden Block zuweisen, da Sie normalerweise nur über lokale Teile iterieren müssen. Die Einführung einer Indirektionsebene wirkt sich nicht sinnvoll auf die Leistung aus.

Matt Knepley
quelle
Irgendwelche Referenzen für diesen Anspruch? Eine einfache matrixfreie Methode sollte einen sehr geringen Löser-Footprint haben.
Aterrel
2
Sicher, wenn Sie matrixfrei arbeiten, kann dies ein anderes Problem sein. Aber kurz davor ist leicht zu erkennen, dass Matts Kommentar richtig ist: Für jeden Freiheitsgrad auf dem Netz muss die Matrix so viele Doppelte speichern, wie dieser DoF koppelt - was in allen nicht trivialen 3D-Fällen zu Hunderten führt (Zählen Sie dies einmal für ein Q2-Q1 / Taylor-Hood Stokes-Element in 3d). Sie werden leicht erkennen, dass dies viel mehr Speicher benötigt als die Daten, die das Netz definieren.
Wolfgang Bangerth
0

Wenn Ihnen der Speicher ausgeht, führen Sie einfach mehr Knoten aus, damit Sie mehr Speicher haben. Sie verschwenden eine sehr wertvolle Ressource (das menschliche Gehirn), um ein Problem zu lösen, das sehr einfach zu lösen ist.

Jeff
quelle