it-swarm.com.de

Wie wichtig ist die Speicherausrichtung? Ist es noch wichtig?

Seit einiger Zeit habe ich viel über die Ausrichtung des Speichers gesucht und gelesen, wie es funktioniert und wie man es verwendet. Der relevanteste Artikel, den ich jetzt gefunden habe, ist dieser .

Aber trotzdem habe ich noch einige Fragen dazu:

  1. Aus dem eingebetteten System heraus haben wir oft einen großen Speicherplatz in unserem Computer, der die Speicherverwaltung viel weniger kritisch macht. Ich bin voll und ganz in der Optimierung, aber jetzt ist es wirklich etwas, das den Unterschied ausmachen kann, wenn wir dasselbe Programm mit oder vergleichen ohne dass der Speicher neu angeordnet und ausgerichtet wird?
  2. Hat die Speicherausrichtung andere Vorteile? Ich habe irgendwo gelesen, dass die CPU mit ausgerichtetem Speicher besser/schneller arbeitet, weil die Verarbeitung weniger Anweisungen erfordert (wenn einer von Ihnen einen Link für einen Artikel/Benchmark dazu hat?). Ist der Unterschied in diesem Fall wirklich signifikant? Gibt es mehr Vorteile als diese beiden?
  3. Im Artikel-Link in Kapitel 5 sagt der Autor:

    Achtung: In C++ können Klassen, die wie Strukturen aussehen, gegen diese Regel verstoßen! (Ob dies der Fall ist oder nicht, hängt davon ab, wie Basisklassen und Funktionen virtueller Elemente implementiert sind, und variiert je nach Compiler.)

  4. Der Artikel spricht hauptsächlich über Strukturen, aber ist die Deklaration lokaler Variablen auch von diesem Bedarf betroffen?

    Haben Sie eine Vorstellung davon, wie die Speicherausrichtung in C++ genau funktioniert, da es einige Unterschiede zu geben scheint?

Diese frühere Frage enthält das Wort "Ausrichtung", gibt jedoch keine Antworten auf die obigen Fragen.

16
Kane

Ja, sowohl die Ausrichtung als auch die Anordnung Ihrer Daten können einen großen Unterschied in der Leistung bewirken, nicht nur einige Prozent, sondern nur wenige bis viele Hundert Prozent.

Nehmen Sie diese Schleife, zwei Anweisungen sind wichtig, wenn Sie genügend Schleifen ausführen.

.globl ASMDELAY
ASMDELAY:
    subs r0,r0,#1
    bne ASMDELAY
    bx lr

Mit und ohne Cache und mit Ausrichtung mit und ohne Cache-Wurf in der Verzweigungsvorhersage können Sie die Leistung dieser beiden Befehle um einen erheblichen Betrag variieren (Timer-Ticks):

min      max      difference
00016DDE 003E025D 003C947F

Ein Leistungstest, den Sie ganz einfach selbst durchführen können. Fügen Sie Nops um den zu testenden Code hinzu oder entfernen Sie sie, und führen Sie eine genaue Zeitmessung durch. Verschieben Sie die zu testenden Anweisungen entlang eines ausreichend großen Adressbereichs, um die Kanten der Cache-Zeilen zu berühren usw.

Gleiches gilt für Datenzugriffe. Einige Architekturen beschweren sich über nicht ausgerichtete Zugriffe (z. B. Ausführen eines 32-Bit-Lesevorgangs unter der Adresse 0x1001), indem sie einen Datenfehler melden. Bei einigen davon können Sie den Fehler deaktivieren und den Leistungseinbruch hinnehmen. Bei anderen, die nicht ausgerichtete Zugriffe zulassen, wird nur die Leistung beeinträchtigt.

Es sind manchmal "Anweisungen", aber meistens sind es Takt-/Buszyklen.

Schauen Sie sich die memcpy-Implementierungen in gcc für verschiedene Ziele an. Angenommen, Sie kopieren eine Struktur mit 0 x 43 Byte. Möglicherweise finden Sie eine Implementierung, die ein Byte kopiert, wobei 0 x 42 übrig bleibt, und dann 0 x 40 Byte in großen, effizienten Blöcken kopiert. Das letzte 0 x 2 kann als zwei einzelne Bytes oder als 16-Bit-Übertragung ausgeführt werden. Ausrichtung und Ziel kommen ins Spiel, wenn Quell- und Zieladresse auf derselben Ausrichtung stehen, z. B. 0x1003 und 0x2003, dann könnten Sie das eine Byte, dann 0x40 in großen Blöcken und dann 0x2 ausführen, aber wenn eine 0x1002 und die andere 0x1003 ist, wird es sehr hässlich und sehr langsam.

Meistens sind es Buszyklen. Oder schlimmer noch die Anzahl der Überweisungen. Nehmen Sie einen Prozessor mit einem 64 Bit breiten Datenbus wie ARM und führen Sie eine Vier-Wort-Übertragung (Lesen oder Schreiben, LDM oder STM) unter der Adresse 0x1004 durch, dh einer wortausgerichteten Adresse, die vollkommen legal ist, aber wenn der Bus 64 ist Bits breit ist es wahrscheinlich, dass der einzelne Befehl in drei Übertragungen umgewandelt wird, in diesem Fall ein 32-Bit bei 0x1004, ein 64-Bit bei 0x1008 und ein 32-Bit bei 0x100A. Wenn Sie jedoch dieselbe Anweisung hätten, jedoch unter der Adresse 0x1008, könnte eine einzelne Übertragung mit vier Wörtern unter der Adresse 0x1008 durchgeführt werden. Jeder Übertragung ist eine Rüstzeit zugeordnet. Der Adressunterschied zwischen 0x1004 und 0x1008 kann also um ein Vielfaches schneller sein, sogar/esp, wenn ein Cache verwendet wird, und alle sind Cache-Treffer.

Apropos, selbst wenn Sie zwei Wörter unter der Adresse 0x1000 vs 0x0FFC lesen, wird der 0x0FFC mit Cache-Fehlern zwei Lesevorgänge in der Cache-Zeile verursachen, wobei 0x1000 eine Cache-Zeile ist. Sie haben die Strafe, dass eine Cache-Zeile ohnehin zufällig gelesen wird Zugriff (mehr Daten lesen als verwenden), aber das verdoppelt sich. Wie Ihre Strukturen oder Ihre Daten im Allgemeinen ausgerichtet sind und wie häufig Sie auf diese Daten usw. zugreifen, kann zu Cache-Thrashing führen.

Sie können Ihre Daten so streifen, dass Sie bei der Verarbeitung der Daten, mit denen Sie Räumungen erstellen können, echtes Pech haben und nur einen Bruchteil Ihres Caches verbrauchen können. Wenn Sie durch den Cache springen, kollidiert der nächste Datenblock mit einem vorherigen Blob . Durch Verwechseln Ihrer Daten oder Neuanordnen von Funktionen im Quellcode usw. können Sie Kollisionen erstellen oder entfernen, da nicht alle Caches gleich erstellt werden. Der Compiler wird Ihnen hier nicht weiterhelfen. Sogar das Erkennen des Leistungseinbruchs oder der Leistungsverbesserung liegt bei Ihnen.

All die Dinge, die wir hinzugefügt haben, um die Leistung zu verbessern, breitere Datenbusse, Pipelines, Caches, Verzweigungsvorhersagen, mehrere Ausführungseinheiten/-pfade usw. helfen meistens, aber sie haben alle Schwachstellen, die entweder absichtlich oder versehentlich ausgenutzt werden können. Der Compiler oder die Bibliotheken können nur sehr wenig dagegen tun. Wenn Sie an einer Leistung interessiert sind, die Sie optimieren müssen, ist einer der größten Optimierungsfaktoren die Ausrichtung des Codes und der Daten, nicht nur die Ausrichtung auf 32, 64, 128, 256 Bitgrenzen, aber auch dort, wo die Dinge relativ zueinander sind, möchten Sie, dass stark verwendete Schleifen oder wiederverwendete Daten nicht auf dieselbe Cache-Weise landen, sondern jeweils ihre eigenen. Compiler können beispielsweise beim Ordnen von Anweisungen für eine superskalare Architektur helfen, indem sie Anweisungen neu anordnen, die relativ zueinander keine Rolle spielen, einen großen Leistungsgewinn oder -erfolg erzielen können, wenn Sie die Ausführungspfade nicht effizient nutzen, dies aber mitteilen müssen Compiler, auf dem Sie laufen.

Das größte Versehen ist die Annahme, dass der Prozessor der Engpass ist. Ist seit einem Jahrzehnt oder länger nicht mehr der Fall, ist das Füttern des Prozessors das Problem, und hier kommen Probleme wie Treffer bei der Ausrichtungsleistung, Cache-Thrashing usw. ins Spiel. Mit ein wenig Arbeit auch auf Quellcodeebene kann das Neuanordnen von Daten in einer Struktur, das Ordnen von Variablen-/Strukturdeklarationen, das Ordnen von Funktionen innerhalb des Quellcodes und ein wenig zusätzlicher Code zum Ausrichten von Daten die Leistung um ein Vielfaches verbessern Mehr.

11
old_timer

Ja, die Speicherausrichtung ist immer noch wichtig.

Einige Prozessoren können tatsächlich keine Lesevorgänge für nicht ausgerichtete Adressen ausführen. Wenn Sie auf einer solchen Hardware arbeiten und Ihre Ganzzahlen nicht ausgerichtet speichern, müssen Sie sie wahrscheinlich mit zwei Anweisungen lesen, gefolgt von einigen weiteren Anweisungen, um die verschiedenen Bytes an die richtigen Stellen zu bringen, damit Sie sie tatsächlich verwenden können . Ausgerichtete Daten sind daher leistungskritisch.

Die gute Nachricht ist, dass Sie sich meistens nicht wirklich darum kümmern müssen. Fast jeder Compiler für fast jede Sprache erzeugt Maschinencode, der die Ausrichtungsanforderungen des Zielsystems berücksichtigt. Sie müssen nur darüber nachdenken, wenn Sie die speicherinterne Darstellung Ihrer Daten direkt steuern, was nicht annähernd so oft wie früher erforderlich ist. Es ist eine interessante Sache zu wissen und absolut wichtig zu wissen, ob Sie die Speichernutzung aus verschiedenen Strukturen, die Sie erstellen, verstehen möchten und wie Sie möglicherweise Dinge reorganisieren können, um effizienter zu sein (Auffüllen vermeiden). Aber wenn Sie diese Art der Kontrolle nicht benötigen (und für die meisten Systeme einfach nicht), können Sie glücklich eine ganze Karriere durchlaufen, ohne es zu wissen oder sich darum zu kümmern.

15
Matthew Walton

Ja, es ist immer noch wichtig, und bei einigen leistungskritischen Algorithmen können Sie sich nicht auf den Compiler verlassen.

Ich werde nur einige Beispiele auflisten:

  1. Von diese Antwort :

Normalerweise ruft der Mikrocode die richtige 4-Byte-Menge aus dem Speicher ab. Wenn er jedoch nicht ausgerichtet ist, muss er zwei 4-Byte-Speicherorte aus dem Speicher abrufen und die gewünschte 4-Byte-Menge aus den entsprechenden Bytes der beiden Speicherorte rekonstruieren

  1. Der Befehlssatz SSE erfordert eine spezielle Ausrichtung. Wenn er nicht erfüllt ist, müssen Sie spezielle Funktionen zum Laden und Speichern von Daten in einem nicht ausgerichteten Speicher verwenden. Dies bedeutet zwei zusätzliche Anweisungen.

Wenn Sie nicht an leistungskritischen Algorithmen arbeiten, vergessen Sie einfach die Speicherausrichtung. Es wird für die normale Programmierung nicht wirklich benötigt.

3
BЈовић

Wir neigen dazu, Situationen zu vermeiden, in denen es darauf ankommt. Wenn es darauf ankommt, ist es wichtig. Unausgerichtete Daten traten beispielsweise bei der Verarbeitung von Binärdaten auf, was heutzutage vermieden zu werden scheint (Benutzer verwenden häufig XML oder JSON).

Wenn Sie es irgendwie schaffen, ein nicht ausgerichtetes Array von Ganzzahlen zu erstellen, läuft Ihre Codeverarbeitung dieses Arrays auf einem typischen Intel-Prozessor etwas langsamer als bei ausgerichteten Daten. Auf einem ARM Prozessor) läuft es etwas langsamer, wenn Sie dem Compiler mitteilen, dass die Daten nicht ausgerichtet sind. Je nach Prozessormodell und Betrieb kann es entweder sehr viel langsamer laufen oder falsche Ergebnisse liefern System, wenn Sie nicht ausgerichtete Daten verwenden, ohne dies dem Compiler mitzuteilen.

Erläutern des Verweises auf C++: In C müssen alle Felder in einer Struktur in aufsteigender Speicherreihenfolge gespeichert werden. Wenn Sie also Felder char/double/char haben und alles ausrichten möchten, haben Sie ein Byte char, sieben Byte unbenutzt, acht Byte double, ein Byte char, sieben Byte unbenutzt. In C++ - Strukturen ist dies aus Kompatibilitätsgründen dasselbe. Bei Strukturen kann der Compiler jedoch Felder neu anordnen, sodass Sie möglicherweise ein Byte-Zeichen, ein anderes Byte-Zeichen, sechs nicht verwendete Byte und ein doppeltes 8-Byte-Zeichen haben. Verwenden Sie 16 statt 24 Bytes. In C-Strukturen vermeiden Entwickler diese Situation normalerweise und haben die Felder in erster Linie in einer anderen Reihenfolge.

1
gnasher729

Viele gute Punkte sind bereits in den obigen Antworten erwähnt. Nur in nicht eingebetteten Systemen, die sich mit Datensuche/Mining befassen, ist die Leistung von Speicherangelegenheiten und Zugriffszeiten so wichtig, dass neben der Ausrichtung auch Assembly-Code für denselben geschrieben wird.

Ich empfehle auch eine lohnende Lektüre: http://dewaele.org/~robbe/thesis/writing/references/what-every-programmer-should-know-about-memory.2007.pdf

1
Varun Mishra

Wie wichtig ist die Speicherausrichtung? Ist es noch wichtig?

Ja. Nein, es kommt darauf an.

Aus dem eingebetteten System heraus haben wir oft einen großen Speicherplatz in unserem Computer, der die Speicherverwaltung viel weniger kritisch macht. Ich bin voll und ganz in der Optimierung, aber jetzt ist es wirklich etwas, das den Unterschied ausmachen kann, wenn wir dasselbe Programm mit oder vergleichen ohne dass der Speicher neu angeordnet und ausgerichtet wird?

Ihre Anwendung hat einen geringeren Speicherbedarf und arbeitet schneller, wenn sie richtig ausgerichtet ist. In der typischen Desktop-Anwendung spielt dies außerhalb seltener/atypischer Fälle keine Rolle (z. B. wenn Ihre Anwendung immer mit demselben Leistungsengpass endet und Optimierungen erfordert). Das heißt, die App wird kleiner und schneller, wenn sie richtig ausgerichtet ist, aber in den meisten praktischen Fällen sollte sie den Benutzer nicht auf die eine oder andere Weise beeinflussen.

Hat die Speicherausrichtung andere Vorteile? Ich habe irgendwo gelesen, dass die CPU mit ausgerichtetem Speicher besser/schneller arbeitet, weil die Verarbeitung weniger Anweisungen erfordert (wenn einer von Ihnen einen Link für einen Artikel/Benchmark dazu hat?). Ist der Unterschied in diesem Fall wirklich signifikant? Gibt es mehr Vorteile als diese beiden?

Es kann sein. Es ist etwas, das (möglicherweise) beim Schreiben von Code beachtet werden muss, aber in den meisten Fällen sollte es einfach keine Rolle spielen (das heißt, ich ordne meine Mitgliedsvariablen immer noch nach Speicherbedarf und Zugriffshäufigkeit an - was das Caching erleichtern sollte -, aber ich tue dies für Benutzerfreundlichkeit/Lesen und Umgestalten des Codes, nicht zum Zwischenspeichern).

Haben Sie eine Vorstellung davon, wie die Speicherausrichtung in C++ genau funktioniert, da es einige Unterschiede zu geben scheint?

Ich habe darüber gelesen, als das Alignof-Material herauskam (C++ 11?). Seitdem habe ich mich nicht mehr darum gekümmert (ich mache heutzutage hauptsächlich Desktop-Anwendungen und Backend-Server-Entwicklung).

1
utnapistim