Gibt es Leistungsprobleme bei der Verwendung von Pragma Pack(1)?
Lesezeit: 8 Minuten
Nicolas
Unsere Header verwenden #pragma pack(1) um die meisten unserer Strukturen (wird für Netz- und Datei-I/O verwendet). Ich verstehe, dass dadurch die Ausrichtung von Strukturen vom Standardwert von 8 Byte auf eine Ausrichtung von 1 Byte geändert wird.
Unter der Annahme, dass alles unter 32-Bit-Linux (vielleicht auch unter Windows) läuft, gibt es dann irgendwelche Leistungseinbußen, die sich aus dieser Paketausrichtung ergeben?
Ich mache mir keine Sorgen um die Portabilität von Bibliotheken, sondern eher um die Kompatibilität von Datei- und Netzwerk-E/A mit verschiedenen #pragma-Paketen und um Leistungsprobleme.
Ich wusste nicht einmal, dass GCC unterstützt #pragma pack. Nicht, dass ich es jetzt verwenden werde.
Der Speicherzugriff ist am schnellsten, wenn er an wortausgerichteten Speicheradressen erfolgen kann. Das einfachste Beispiel ist die folgende Struktur (die auch @Didier verwendet hat):
struct sample {
char a;
int b;
};
Standardmäßig fügt GCC Auffüllungen ein, sodass a am Offset 0 und b am Offset 4 liegt (wortausgerichtet). Ohne Auffüllung ist b nicht wortausgerichtet und der Zugriff ist langsamer.
Der Prozessor benötigt zwei Speicherzugriffe, um einen nicht ausgerichteten Speicherzugriff durchzuführen; ausgerichtete Zugriffe erfordern nur einen Speicherzugriff. Ein Wort- oder Doppelwortoperand, der eine 4-Byte-Grenze überschreitet, oder ein Quadword-Operand, der eine 8-Byte-Grenze überschreitet, gilt als nicht ausgerichtet und erfordert zwei separate Speicherbuszyklen für den Zugriff.
Wie bei den meisten Leistungsfragen müssen Sie Ihre Anwendung einem Benchmarking unterziehen, um zu sehen, wie problematisch dies in der Praxis ist.
Entsprechend Wikipediax86-Erweiterungen wie SSE2 erfordern Wortausrichtung.
Viele andere Architekturen erfordern eine Wortausrichtung (und erzeugen SIGBUS-Fehler, wenn Datenstrukturen nicht wortausgerichtet sind).
Bezüglich der Portabilität: Ich gehe davon aus, dass Sie es verwenden #pragma pack(1) So können Sie Strukturen über die Leitung und von und zur Festplatte senden, ohne sich Gedanken darüber machen zu müssen, dass verschiedene Compiler oder Plattformen Strukturen unterschiedlich verpacken. Dies ist zwar gültig, es sind jedoch einige Punkte zu beachten:
Dies trägt nicht dazu bei, Big-Endian- und Little-Endian-Probleme zu lösen. Sie können diese Probleme lösen, indem Sie die anrufen htons Familie von Funktionen für alle Ints, Unsigned usw. in Ihren Strukturen.
Meiner Erfahrung nach macht die Arbeit mit gepackten, serialisierbaren Strukturen im Anwendungscode nicht viel Spaß. Es ist sehr schwierig, sie zu ändern und zu erweitern, ohne die Abwärtskompatibilität zu beeinträchtigen, und wie bereits erwähnt, gibt es Leistungseinbußen. Erwägen Sie, den Inhalt Ihrer gepackten, serialisierbaren Strukturen zur Verarbeitung in entsprechende, nicht gepackte, erweiterbare Strukturen zu übertragen, oder erwägen Sie die Verwendung einer vollwertigen Serialisierungsbibliothek wie z Protokollpuffer (was hat C-Bindungen).
+1 für eine hervorragende Antwort und für den Hinweis, dass es tatsächlich einige Nicht-x86-Architekturen gibt erfordern richtige Ausrichtung für bestimmte Datentypen.
– Paul R
17. Okt. 2011 um 12:47
Endinaness wird nicht wirklich behandelt, aber es ist „OK“, da unser gesamtes Backoffice Linux-gesteuert ist. Ich werde tatsächlich einen Benchmark durchführen und vielleicht hier darüber berichten. Danke für die Antwort.
– Nicolas
17. Okt. 2011 um 13:22
Ja. Gibt es auf jeden Fall.
Wenn Sie beispielsweise eine Struktur definieren:
struct dumb {
char c;
int i;
};
Wenn Sie dann auf das Mitglied i zugreifen, wird die CPU verlangsamt, da auf den 32-Bit-Wert i nicht auf native, ausgerichtete Weise zugegriffen werden kann. Stellen Sie sich zur Vereinfachung vor, dass die CPU 3 Bytes aus dem Speicher und dann 1 weiteres Byte vom nächsten Speicherort abrufen muss, um den Wert aus dem Speicher in die CPU-Register zu übertragen.
Alok Save
Wenn Sie eine Struktur deklarieren, fügen die meisten Compiler Füllbytes zwischen den Mitgliedern ein, um sicherzustellen, dass sie an den richtigen Adressen im Speicher ausgerichtet sind (normalerweise sind die Füllbytes ein Vielfaches der Typgröße). Dies ermöglicht dem Compiler einen optimierten Zugriff beim Zugriff auf diese Mitglieder.
#pragma pack(1) weist den Compiler an, Strukturelemente mit einer bestimmten Ausrichtung zu packen. Der 1 Hier wird der Compiler angewiesen, keine Auffüllungen zwischen Mitgliedern einzufügen.
So Ja, es gibt eindeutig eine Leistungseinbußeda Sie den Compiler dazu zwingen, etwas zu tun, das über das hinausgeht, was er normalerweise zur Leistungsoptimierung tun würde. Außerdem Einige Plattformen verlangen, dass die Objekte an bestimmten Grenzen ausgerichtet werden, und die Verwendung nicht ausgerichteter Strukturen kann zu Segmentierungsfehlern führen.
Im Idealfall ist es am besten, eine Änderung der Standardregeln für die natürliche Ausrichtung zu vermeiden. Lässt sich die „Pragma Pack“-Direktive jedoch überhaupt nicht vermeiden (wie in Ihrem Fall), muss nach der Definition der Strukturen, die eine dichte Packung erfordern, das ursprüngliche Packungsschema wiederhergestellt werden.
Zum Beispiel:
//push current alignment rules to internal stack and force 1-byte alignment boundary
#pragma pack(push,1)
/* definition of structures that require tight packing go in here */
//restore original alignment rules from stack
#pragma pack(pop)
Oder besser: Verwenden Sie die native Version von gcc aligned Attribut um nur die aktuelle Struktur zu markieren.
– Blagovest Büyukliew
17. Okt. 2011 um 12:37
Blagovest Büyukliew
Dies hängt von der zugrunde liegenden Architektur und der Art und Weise ab, wie sie mit nicht ausgerichteten Adressen umgeht.
x86 verarbeitet nicht ausgerichtete Adressen ordnungsgemäß, wenn auch mit Leistungseinbußen, während andere Architekturen wie ARM möglicherweise einen Ausrichtungsfehler hervorrufen (SIGBUS) oder „runden“ Sie sogar die falsch ausgerichtete Adresse auf die nächste Grenze. In diesem Fall wird Ihr Code auf abscheuliche Weise fehlschlagen.
Fazit: Packen Sie es nur, wenn Sie sicher sind, dass die zugrunde liegende Architektur nicht ausgerichtete Adressen verarbeiten kann und wenn die Kosten für Netzwerk-E/A höher sind als die Verarbeitungskosten.
Gibt es Leistungsprobleme bei der Verwendung von Pragma Pack(1)?
Absolut. Im Januar 2020 veröffentlichte Raymond Chen von Microsoft konkrete Beispiele für die Verwendung #pragma pack(1) kann aufgeblähte ausführbare Dateien erzeugen, die sehr viel mehr Anweisungen benötigen, um Operationen an gepackten Strukturen auszuführen. Insbesondere auf Nicht-x86-Hardware, die falsch ausgerichtete Zugriffe in der Hardware nicht direkt unterstützt.
Wenn Sie verwenden #pragma pack(1)ändert dies die Standardstrukturpackung in Bytepackung und entfernt alle Füllbytes, die normalerweise eingefügt werden, um die Ausrichtung beizubehalten.
…
Die Möglichkeit, dass eine P-Struktur falsch ausgerichtet sein könnte, hat erhebliche Konsequenzen für die Codegenerierung, da alle Zugriffe auf Mitglieder den Fall behandeln müssen, dass die Adresse nicht richtig ausgerichtet ist.
Beachten Sie, dass bei einigen RISC-Prozessoren die Codegrößenexplosion erheblich ist. Dies kann wiederum Auswirkungen auf Inlining-Entscheidungen haben.
Moral der Geschichte: Bewerben Sie sich nicht #pragma pack(1) an Bauwerken, es sei denn, dies ist unbedingt erforderlich. Es bläht Ihren Code auf und verhindert Optimierungen.
#pragma pack(1) und seine Variationen sind auch auf subtile Weise gefährlich – sogar auf x86-Systemen, wo sie angeblich „funktionieren“
Ioan
Technisch gesehen würde es zwar die Leistung beeinträchtigen, aber nur im Hinblick auf die interne Verarbeitung. Wenn Sie die Strukturen für Netzwerk-/Datei-E/A gepackt benötigen, besteht ein Gleichgewicht zwischen der gepackten Anforderung und der rein internen Verarbeitung. Mit interner Verarbeitung meine ich die Arbeit, die Sie an den Daten zwischen den E/A durchführen. Wenn Sie nur sehr wenig verarbeiten, verlieren Sie nicht viel an Leistung. Andernfalls möchten Sie möglicherweise eine interne Verarbeitung für ordnungsgemäß ausgerichtete Strukturen durchführen und die Ergebnisse nur bei der Durchführung von E/A-Vorgängen „packen“. Oder Sie könnten dazu wechseln, nur standardmäßig ausgerichtete Strukturen zu verwenden, müssen jedoch sicherstellen, dass alle diese auf die gleiche Weise ausrichten (Netzwerk- und Datei-Clients).
Ole Dittmann
Es gibt bestimmte Maschinencodeanweisungen, die mit 32 Bit oder 64 Bit (oder sogar mehr) arbeiten, aber erwarten, dass die Daten an Speicheradressen ausgerichtet sind. Ist dies nicht der Fall, müssen sie mehr als einen Lese-/Schreibzyklus im Speicher ausführen, um ihre Aufgabe auszuführen. Wie stark dieser Leistungseinbruch ausfällt, hängt stark davon ab, was Sie mit den Daten machen. Wenn Sie große Arrays von Strukturen erstellen und umfangreiche Berechnungen daran durchführen, kann es zu großen Arrays kommen. Wenn Sie Daten jedoch nur einmal speichern, um sie zu einem späteren Zeitpunkt zurückzulesen und sie ohnehin in einen Byte-Stream umzuwandeln, fallen sie möglicherweise kaum auf.
14517700cookie-checkGibt es Leistungsprobleme bei der Verwendung von Pragma Pack(1)?yes
Ich wusste nicht einmal, dass GCC unterstützt
#pragma pack
. Nicht, dass ich es jetzt verwenden werde.– Fred Foo
17. Okt. 2011 um 12:13
@larsmans Ja, aufgrund von Windows: gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html
– Nicolas
17. Okt. 2011 um 12:16 Uhr