Zweck der Speicherausrichtung

195

Zugegeben, ich verstehe es nicht. Angenommen, Sie haben einen Speicher mit einem Speicherwort mit einer Länge von 1 Byte. Warum können Sie nicht auf eine 4 Byte lange Variable in einem einzelnen Speicherzugriff auf eine nicht ausgerichtete Adresse zugreifen (dh nicht durch 4 teilbar), wie dies bei ausgerichteten Adressen der Fall ist?

Arche
quelle
17
Nachdem ich ein bisschen zusätzlich gegoogelt hatte, fand ich diesen großartigen Link, der das Problem wirklich gut erklärt.
Arche
Schauen Sie sich diesen kleinen Artikel für Leute an, die anfangen, dies zu lernen: blog.virtualmethodstudio.com/2017/03/memory-alignment-run-fools
darkgaze
3
@ark Link gebrochen
John Jiang
2
@ JohnJiang Ich glaube, ich habe den neuen Link hier gefunden: developer.ibm.com/technologies/systems/articles/pa-dalign
ejohnso49

Antworten:

62

Dies ist eine Einschränkung vieler zugrunde liegender Prozessoren. Es kann normalerweise umgangen werden, indem 4 ineffiziente Einzelbyte-Abrufe anstelle eines effizienten Wortabrufs durchgeführt werden. Viele Sprachspezifizierer entschieden jedoch, dass es einfacher ist, sie nur zu verbieten und die Ausrichtung zu erzwingen.

Dieser Link enthält viel mehr Informationen , die das OP entdeckt hat.

Paul Tomblin
quelle
310

Das Speichersubsystem eines modernen Prozessors ist auf den Zugriff auf Speicher mit der Granularität und Ausrichtung seiner Wortgröße beschränkt. Dies ist aus mehreren Gründen der Fall.

Geschwindigkeit

Moderne Prozessoren verfügen über mehrere Ebenen des Cache-Speichers, durch die Daten gezogen werden müssen. Das Unterstützen von Einzelbyte-Lesevorgängen würde den Durchsatz des Speichersubsystems eng an den Durchsatz der Ausführungseinheit binden (auch bekannt als CPU-gebunden). Dies alles erinnert daran, wie der PIO-Modus von DMA aus vielen der gleichen Gründe auf Festplatten übertroffen wurde .

Die CPU liest immer mit ihrer Wortgröße (4 Byte auf einem 32-Bit-Prozessor). Wenn Sie also einen nicht ausgerichteten Adresszugriff durchführen - auf einem Prozessor, der dies unterstützt -, liest der Prozessor mehrere Wörter. Die CPU liest jedes Speicherwort, das Ihre angeforderte Adresse überspannt. Dies bewirkt eine bis zu zweifache Verstärkung der Anzahl von Speichertransaktionen, die für den Zugriff auf die angeforderten Daten erforderlich sind.

Aus diesem Grund kann es sehr leicht langsamer sein, zwei Bytes als vier zu lesen. Angenommen, Sie haben eine Struktur im Speicher, die folgendermaßen aussieht:

struct mystruct {
    char c;  // one byte
    int i;   // four bytes
    short s; // two bytes
}

Auf einem 32-Bit-Prozessor würde es höchstwahrscheinlich wie hier gezeigt ausgerichtet sein:

Strukturlayout

Der Prozessor kann jedes dieser Mitglieder in einer Transaktion lesen.

Angenommen, Sie hatten eine gepackte Version der Struktur, möglicherweise aus dem Netzwerk, in dem sie aus Gründen der Übertragungseffizienz gepackt wurde. es könnte ungefähr so ​​aussehen:

Gepackte Struktur

Das Lesen des ersten Bytes wird dasselbe sein.

Wenn Sie den Prozessor bitten, Ihnen 16 Bit von 0x0005 zu geben, muss er ein Wort von 0x0004 lesen und 1 Byte nach links verschieben, um es in ein 16-Bit-Register zu legen. etwas zusätzliche Arbeit, aber die meisten können das in einem Zyklus erledigen.

Wenn Sie von 0x0001 nach 32 Bit fragen, erhalten Sie eine 2-fache Verstärkung. Der Prozessor liest von 0x0000 in das Ergebnisregister und verschiebt 1 Byte nach links, liest dann erneut von 0x0004 in ein temporäres Register, verschiebt 3 Byte nach rechts und dann ORmit dem Ergebnisregister.

Angebot

Wenn die Architektur für einen bestimmten Adressraum davon ausgehen kann, dass die 2 LSBs immer 0 sind (z. B. 32-Bit-Maschinen), kann sie auf viermal mehr Speicher zugreifen (die zwei gespeicherten Bits können vier verschiedene Zustände darstellen) oder auf dieselbe Menge Speicher mit 2 Bits für so etwas wie Flags. Wenn Sie die 2 LSBs von einer Adresse entfernen, erhalten Sie eine 4-Byte-Ausrichtung. wird auch als Schritt von 4 Bytes bezeichnet. Jedes Mal, wenn eine Adresse inkrementiert wird, wird Bit 2 und nicht Bit 0 effektiv inkrementiert, dh die letzten 2 Bits bleiben immer bestehen 00.

Dies kann sich sogar auf das physische Design des Systems auswirken. Wenn der Adressbus 2 weniger Bits benötigt, können 2 weniger Pins auf der CPU und 2 weniger Spuren auf der Leiterplatte vorhanden sein.

Atomizität

Die CPU kann atomar mit einem ausgerichteten Speicherwort arbeiten, was bedeutet, dass kein anderer Befehl diesen Vorgang unterbrechen kann. Dies ist entscheidend für den korrekten Betrieb vieler sperrenfreier Datenstrukturen und anderer Parallelitätsparadigmen .

Fazit

Das Speichersystem eines Prozessors ist wesentlich komplexer und komplizierter als hier beschrieben; Eine Diskussion darüber, wie ein x86-Prozessor tatsächlich den Speicher adressiert, kann helfen (viele Prozessoren arbeiten ähnlich).

Die Einhaltung der Speicherausrichtung bietet noch viele weitere Vorteile, die Sie in diesem IBM Artikel lesen können .

Die Hauptverwendung eines Computers besteht darin, Daten zu transformieren. Moderne Speicherarchitekturen und -technologien wurden über Jahrzehnte hinweg optimiert, um das Ein- und Auslesen von mehr Daten zwischen mehr und schnelleren Ausführungseinheiten auf äußerst zuverlässige Weise zu ermöglichen.

Bonus: Caches

Eine andere Ausrichtung für die Leistung, auf die ich zuvor hingewiesen habe, ist die Ausrichtung auf Cache-Zeilen, die (zum Beispiel auf einigen CPUs) 64B sind.

Weitere Informationen darüber, wie viel Leistung durch die Nutzung von Caches erzielt werden kann, finden Sie in der Galerie der Prozessor-Cache-Effekte . von dieser Frage auf Cache-Zeilengrößen

Das Verständnis der Cache-Zeilen kann für bestimmte Arten von Programmoptimierungen wichtig sein. Beispielsweise kann die Ausrichtung von Daten bestimmen, ob eine Operation eine oder zwei Cache-Zeilen berührt. Wie wir im obigen Beispiel gesehen haben, kann dies leicht bedeuten, dass im falsch ausgerichteten Fall der Vorgang zweimal langsamer ist.

Joshperry
quelle
Die folgenden Strukturen xyz haben unterschiedliche Größen, da die Regel jedes Mitglieds mit der Adresse beginnen muss, die ein Vielfaches seiner Größe ist, und der Strcut mit einer Adresse enden muss, die ein Vielfaches der größten Größe des Mitglieds der Struktur ist. struct x {short s; // 2 Bytes und 2 Auffüllungstypen int i; // 4 Bytes char c; // 1 Byte und 3 Füllbytes lang lang l; }; struct y {int i; // 4 Bytes char c; // 1 Byte und 1 Füllbyte kurz s; // 2 Bytes}; struct z {int i; // 4 Bytes kurz s; // 2 Bytes char c; // 1 Byte und 1 Füllbyte};
Gavin
1
Wenn ich das richtig verstehe, liegt der Grund, warum ein Computer ein nicht ausgerichtetes Wort nicht in einem Schritt lesen kann, darin, dass die Addessen 30 Bit und nicht 32 Bit verwenden?
GetFree
1
@chux Ja es ist wahr, Absolutes halten nie. Der 8088 ist eine interessante Studie über die Kompromisse zwischen Geschwindigkeit und Kosten. Es handelte sich im Grunde genommen um einen 16-Bit-8086 (der einen vollständigen externen 16-Bit-Bus hatte), aber mit nur der Hälfte der Busleitungen, um Produktionskosten zu sparen. Aus diesem Grund benötigte der 8088 doppelt so viele Taktzyklen, um auf den Speicher zuzugreifen wie der 8086, da er zwei Lesevorgänge ausführen musste, um das vollständige 16-Bit-Wort zu erhalten. Der interessante Teil, der 8086 kann ein wortausgerichtetes 16-Bit-Lesen in einem einzigen Zyklus ausführen, nicht ausgerichtete Lesevorgänge dauern 2. Die Tatsache, dass der 8088 einen Halbwortbus hatte, maskierte diese Verlangsamung.
Joshperry
2
@joshperry: Leichte Korrektur: Der 8086 kann in vier Zyklen einen wortausgerichteten 16-Bit-Lesevorgang ausführen , während nicht ausgerichtete Lesevorgänge acht dauern . Aufgrund der langsamen Speicherschnittstelle wird die Ausführungszeit auf 8088-basierten Computern normalerweise von Befehlsabrufen dominiert. Ein Befehl wie "MOV AX, BX" ist nominell einen Zyklus schneller als "XCHG AX, BX", aber wenn ihm kein Befehl vorausgeht oder folgt, dessen Ausführung mehr als vier Zyklen pro Codebyte dauert, dauert es vier Zyklen länger ausführen. Auf dem 8086 kann das Abrufen von Code manchmal mit der Ausführung Schritt halten, auf dem 8088 jedoch, es sei denn, man verwendet ...
Supercat
1
Sehr wahr, @martin. Ich habe diese Füllbytes entfernt, um die Diskussion innerhalb der Struktur zu fokussieren, aber vielleicht wäre es besser, sie einzuschließen.
Joshperry
22

Sie können mit einigen Prozessoren ( das Nehalem kann dies tun ), aber zuvor war der gesamte Speicherzugriff auf einer 64-Bit- (oder 32-Bit-) Leitung ausgerichtet, da der Bus 64 Bit breit ist, mussten Sie jeweils 64 Bit abrufen und es war wesentlich einfacher, diese in ausgerichteten "Blöcken" von 64 Bit abzurufen.

Wenn Sie also ein einzelnes Byte erhalten möchten, haben Sie den 64-Bit-Block abgerufen und dann die nicht gewünschten Bits maskiert. Einfach und schnell, wenn sich Ihr Byte am richtigen Ende befand, aber wenn es sich in der Mitte dieses 64-Bit-Blocks befand, müssten Sie die unerwünschten Bits maskieren und die Daten dann an die richtige Stelle verschieben. Schlimmer noch, wenn Sie eine 2-Byte-Variable wollten, die jedoch auf 2 Blöcke aufgeteilt war, erforderte dies das Doppelte der erforderlichen Speicherzugriffe.

Da jeder der Meinung ist, dass Speicher billig ist, hat er den Compiler dazu gebracht, die Daten an den Blockgrößen des Prozessors auszurichten, damit Ihr Code auf Kosten des verschwendeten Speichers schneller und effizienter ausgeführt wird.

gbjbaanb
quelle
5

Grundsätzlich liegt der Grund darin, dass der Speicherbus eine bestimmte Länge hat, die viel, viel kleiner als die Speichergröße ist.

Die CPU liest also aus dem On-Chip-L1-Cache aus, der heutzutage häufig 32 KB beträgt. Der Speicherbus, der den L1-Cache mit der CPU verbindet, hat jedoch die erheblich geringere Breite der Cache-Zeilengröße. Dies liegt in der Größenordnung von 128 Bit .

So:

262,144 bits - size of memory
    128 bits - size of bus

Falsch ausgerichtete Zugriffe überlappen gelegentlich zwei Cache-Zeilen, und dies erfordert einen völlig neuen Cache-Lesevorgang, um die Daten zu erhalten. Es könnte sogar bis zum DRAM fehlen.

Darüber hinaus muss ein Teil der CPU auf dem Kopf stehen, um aus diesen beiden unterschiedlichen Cache-Zeilen, die jeweils einen Teil der Daten enthalten, ein einzelnes Objekt zusammenzusetzen. In einer Zeile befinden sich die Bits sehr hoher Ordnung, in der anderen die Bits sehr niedriger Ordnung.

Es wird dedizierte Hardware geben, die vollständig in die Pipeline integriert ist und das Verschieben ausgerichteter Objekte auf die erforderlichen Bits des CPU-Datenbusses handhabt. Bei falsch ausgerichteten Objekten fehlt diese Hardware jedoch möglicherweise, da es wahrscheinlich sinnvoller ist, diese Transistoren zur Beschleunigung der korrekten Optimierung zu verwenden Programme.

In jedem Fall würde der zweite Speicherlesevorgang, der manchmal erforderlich ist, die Pipeline verlangsamen, unabhängig davon, wie viel Spezialhardware (hypothetisch und dumm) für das Patchen falsch ausgerichteter Speicheroperationen vorgesehen war.

DigitalRoss
quelle
5

@joshperry hat diese Frage hervorragend beantwortet. Zusätzlich zu seiner Antwort habe ich einige Zahlen, die die beschriebenen Effekte grafisch darstellen, insbesondere die 2X-Verstärkung. Hier ist ein Link zu einer Google-Tabelle, der zeigt, wie sich unterschiedliche Wortausrichtungen auswirken. Außerdem ist hier ein Link zu einem Github-Kern mit dem Code für den Test. Der Testcode wurde aus dem Artikel von Jonathan Rentzsch übernommen, auf den @joshperry verwies. Die Tests wurden auf einem Macbook Pro mit einem Quad-Core-Intel Core i7 64-Bit-Prozessor mit 2,8 GHz und 16 GB RAM durchgeführt.

Geben Sie hier die Bildbeschreibung ein

Adino
quelle
4
Was bedeuten xund ykoordinieren?
Shuva
1
Welcher Generationskern i7? (Danke, dass du Links zum Code
gepostet hast
2

Wenn ein System mit byteadressierbarem Speicher über einen 32 Bit breiten Speicherbus verfügt, bedeutet dies, dass effektiv vier byteweite Speichersysteme vorhanden sind, die alle zum Lesen oder Schreiben derselben Adresse verdrahtet sind. Für einen ausgerichteten 32-Bit-Lesevorgang sind Informationen erforderlich, die in allen vier Speichersystemen unter derselben Adresse gespeichert sind, sodass alle Systeme gleichzeitig Daten liefern können. Ein nicht ausgerichteter 32-Bit-Lesevorgang würde erfordern, dass einige Speichersysteme Daten von einer Adresse und einige Daten von der nächsthöheren Adresse zurückgeben. Obwohl es einige Speichersysteme gibt, die so optimiert sind, dass sie solche Anforderungen erfüllen können (zusätzlich zu ihrer Adresse haben sie effektiv ein "Plus Eins" -Signal, wodurch sie eine Adresse verwenden, die höher als angegeben ist), verursacht eine solche Funktion erhebliche Kosten und Komplexität eines Speichersystems;

Superkatze
quelle
2

Wenn Sie einen 32-Bit-Datenbus haben, beginnen die mit dem Speicher verbundenen Adressbus-Adressleitungen bei A 2 , sodass in einem einzigen Buszyklus nur auf 32-Bit-ausgerichtete Adressen zugegriffen werden kann.

Wenn also ein Wort eine Adressausrichtungsgrenze überspannt - dh A 0 für 16/32-Bit-Daten oder A 1 für 32-Bit-Daten nicht Null sind, sind zwei Buszyklen erforderlich, um die Daten zu erhalten.

Einige Architekturen / Befehlssätze unterstützen keinen nicht ausgerichteten Zugriff und generieren bei solchen Versuchen eine Ausnahme. Daher erfordert der vom Compiler generierte nicht ausgerichtete Zugriffscode nicht nur zusätzliche Buszyklen, sondern auch zusätzliche Befehle, was ihn noch weniger effizient macht.

Clifford
quelle
0

Auf PowerPC können Sie problemlos eine Ganzzahl von einer ungeraden Adresse laden.

Sparc und I86 und (glaube ich) Itatnium lösen Hardware-Ausnahmen aus, wenn Sie dies versuchen.

Eine 32-Bit-Last gegenüber vier 8-Bit-Lasten wird auf den meisten modernen Prozessoren keinen großen Unterschied machen. Ob sich die Daten bereits im Cache befinden oder nicht, hat eine weitaus größere Auswirkung.

James Anderson
quelle
Auf Sparc war dies ein "Busfehler", daher das Kapitel "Busfehler, nehmen Sie den Zug" in Peter Van der Lindens "Expert C Programming: Deep C Secrets"
jjg