#Pragmapack-Effekt

Lesezeit: 8 Minuten

Benutzeravatar von Cenoc
Cenoc

Ich habe mich gefragt, ob mir jemand erklären könnte, was das ist #pragma pack Präprozessor-Anweisung tut, und was noch wichtiger ist, warum man sie verwenden möchte.

Ich habe mir die angeschaut MSDN-Seite, was einige Einblicke bot, aber ich hatte gehofft, mehr von Leuten mit Erfahrung zu hören. Ich habe es schon einmal im Code gesehen, obwohl ich anscheinend nicht mehr finde, wo.

  • Es erzwingt eine bestimmte Ausrichtung/Packung einer Struktur, aber wie alle #pragma Richtlinien sind sie implementierungsdefiniert.

    – Traumlax

    23. Juli 2010 um 13:20 Uhr


Benutzeravatar von Nick Meyer
Nik Meyer

#pragma pack weist den Compiler an, Strukturmitglieder mit einer bestimmten Ausrichtung zu packen. Wenn Sie eine Struktur deklarieren, fügen die meisten Compiler Füllzeichen zwischen Membern ein, um sicherzustellen, dass sie an den entsprechenden Adressen im Speicher ausgerichtet sind (normalerweise ein Vielfaches der Größe des Typs). Dies vermeidet Leistungseinbußen (oder regelrechte Fehler) bei einigen Architekturen, die mit dem Zugriff auf Variablen verbunden sind, die nicht richtig ausgerichtet sind. Zum Beispiel gegebene 4-Byte-Ganzzahlen und die folgende Struktur:

struct Test
{
   char AA;
   int BB;
   char CC;
};

Der Compiler könnte sich dafür entscheiden, die Struktur wie folgt im Speicher anzulegen:

|   1   |   2   |   3   |   4   |  

| AA(1) | pad.................. |
| BB(1) | BB(2) | BB(3) | BB(4) | 
| CC(1) | pad.................. |

und sizeof(Test) wäre 4 × 3 = 12, obwohl es nur 6 Byte Daten enthält. Der häufigste Anwendungsfall für die #pragma (meines Wissens) ist bei der Arbeit mit Hardwaregeräten, wo Sie sicherstellen müssen, dass der Compiler keine Auffüllung in die Daten einfügt und jedes Mitglied dem vorherigen folgt. Mit #pragma pack(1)würde die obige Struktur wie folgt aufgebaut sein:

|   1   |

| AA(1) |
| BB(1) |
| BB(2) |
| BB(3) |
| BB(4) |
| CC(1) |

Und sizeof(Test) wäre 1 × 6 = 6.

Mit #pragma pack(2)würde die obige Struktur wie folgt aufgebaut sein:

|   1   |   2   | 

| AA(1) | pad.. |
| BB(1) | BB(2) |
| BB(3) | BB(4) |
| CC(1) | pad.. |

Und sizeof(Test) wäre 2 × 4 = 8.

Die Reihenfolge der Variablen in struct ist ebenfalls wichtig. Mit Variablen wie folgt geordnet:

struct Test
{
   char AA;
   char CC;
   int BB;
};

und mit #pragma pack(2)würde die Struktur wie folgt aufgebaut sein:

|   1   |   2   | 

| AA(1) | CC(1) |
| BB(1) | BB(2) |
| BB(3) | BB(4) |

und sizeOf(Test) wäre 3 × 2 = 6.

  • Es könnte sich lohnen, die Nachteile des Packens hinzuzufügen. (Nicht ausgerichtete Objektzugriffe sind langsam im Beste Fall, führt aber auf einigen Plattformen zu Fehlern.)

    – jalf

    23. Juli 2010 um 14:55 Uhr

  • Scheint, dass die erwähnte “Leistungseinbuße” der Ausrichtungen auf einigen Systemen tatsächlich von Vorteil sein könnte danluu.com/3c-conflict .

    Benutzer152949

    3. Januar 2014 um 15:22 Uhr

  • @ Pacerier Nicht wirklich. Dieser Beitrag spricht von einer ziemlich extremen Ausrichtung (Ausrichtung auf 4-KB-Grenzen). Die CPU erwartet bestimmte Mindestausrichtungen für verschiedene Datentypen, aber diese erfordern im schlimmsten Fall eine 8-Byte-Ausrichtung (ohne Vektortypen zu zählen, die möglicherweise eine 16- oder 32-Byte-Ausrichtung erfordern). Wenn Sie sich nicht an diesen Grenzen ausrichten, führt dies im Allgemeinen zu einem spürbaren Leistungseinbruch (da ein Ladevorgang möglicherweise als zwei Operationen statt als eine ausgeführt werden muss), aber der Typ ist entweder gut ausgerichtet oder nicht. Eine strengere Ausrichtung bringt Ihnen nichts (und ruiniert die Cache-Nutzung

    – jalf

    14. Mai 2015 um 18:30 Uhr

  • Mit anderen Worten, ein Double erwartet, dass es sich an einer 8-Byte-Grenze befindet. Wenn Sie es auf eine 7-Byte-Grenze setzen, wird die Leistung beeinträchtigt. Aber wenn Sie es auf eine 16-, 32-, 64- oder 4096-Byte-Grenze setzen, kaufen Sie nichts über das hinaus, was Ihnen die 8-Byte-Grenze bereits gegeben hat. Sie erhalten die gleiche Leistung von der CPU, während Sie aus den in diesem Beitrag beschriebenen Gründen eine viel schlechtere Cache-Auslastung erhalten.

    – jalf

    14. Mai 2015 um 18:30 Uhr


  • Die Lektion lautet also nicht „Packen ist vorteilhaft“ (Packen verletzt die natürliche Ausrichtung der Typen, sodass die Leistung beeinträchtigt wird), sondern einfach „nicht über das erforderliche Maß hinaus ausrichten“.

    – jalf

    14. Mai 2015 um 18:32 Uhr

Benutzeravatar von nmichaels
nmichaels

#pragma wird verwendet, um nicht-portable (wie nur in diesem Compiler) Nachrichten an den Compiler zu senden. Dinge wie das Deaktivieren bestimmter Warnungen und Packstrukturen sind häufige Gründe. Das Deaktivieren bestimmter Warnungen ist besonders nützlich, wenn Sie mit aktiviertem Flag „Warnungen als Fehler“ kompilieren.

#pragma pack wird speziell verwendet, um anzugeben, dass die Mitglieder der zu packenden Struktur nicht ausgerichtet sein sollen. Es ist nützlich, wenn Sie eine speicherabgebildete Schnittstelle zu einer Hardware haben und in der Lage sein müssen, genau zu steuern, wohin die verschiedenen Strukturmitglieder zeigen. Es ist insbesondere keine gute Geschwindigkeitsoptimierung, da die meisten Maschinen viel schneller mit ausgerichteten Daten umgehen können.

Um es später rückgängig zu machen, wickeln Sie es ein #pragma pack(push,1) und #pragma pack(pop)

  • Um dies nachträglich rückgängig zu machen, tun Sie dies: #pragma pack(push,1) und #pragma pack(pop)

    – malhal

    12. Januar 2014 um 20:14 Uhr


  • @malhal Dies sollte Teil einer Antwort sein. Ich bin hierher gekommen, um genau das zu suchen

    – MANA624

    22. Juni 2021 um 18:49 Uhr


  • @ MANA624 danke, ich habe meinen Kommentar zur Antwort hinzugefügt

    – malhal

    23. Juni 2021 um 13:05 Uhr

  • @malhal Ich kann dir nicht genug danken, das ist so wertvoll

    – Gregor Watters Härtl

    2. August um 0:50 Uhr

Es teilt dem Compiler die Grenze mit, an der Objekte in einer Struktur ausgerichtet werden sollen. Wenn ich zum Beispiel so etwas habe:

struct foo { 
    char a;
    int b;
};

Bei einem typischen 32-Bit-Computer “möchten” Sie normalerweise 3 Byte Auffüllung dazwischen haben a und b so dass b landet an einer 4-Byte-Grenze, um die Zugriffsgeschwindigkeit zu maximieren (und das wird normalerweise standardmäßig passieren).

Wenn Sie jedoch eine extern definierte Struktur abgleichen müssen, möchten Sie sicherstellen, dass der Compiler Ihre Struktur genau gemäß dieser externen Definition anlegt. In diesem Fall können Sie dem Compiler a #pragma pack(1) es zu erzählen nicht um eine Auffüllung zwischen Mitgliedern einzufügen — wenn die Definition der Struktur eine Auffüllung zwischen Mitgliedern beinhaltet, fügen Sie sie explizit ein (z. B. normalerweise mit den Mitgliedern benannt unusedN oder ignoreNoder etwas in dieser Reihenfolge).

  • “Normalerweise “möchten” Sie 3 Byte Auffüllung zwischen a und b haben, damit b an einer 4-Byte-Grenze landet, um die Zugriffsgeschwindigkeit zu maximieren” – wie würde eine Auffüllung von 3 Byte die Zugriffsgeschwindigkeit maximieren?

    – Ashwin

    31. März 2014 um 13:04 Uhr

  • @Ashwin: Platzierung b an einer 4-Byte-Grenze bedeutet, dass der Prozessor sie laden kann, indem er ein einzelnes 4-Byte-Laden ausgibt. Obwohl es etwas vom Prozessor abhängt, besteht eine gute Chance, dass der Prozessor zum Laden zwei separate Ladeanweisungen ausgeben muss, wenn es an einer ungeraden Grenze liegt, und dann einen Schieber verwenden, um diese Teile zusammenzusetzen. Eine typische Strafe liegt in der Größenordnung von 3x langsamerem Laden dieses Elements.

    – Jerry Sarg

    31. März 2014 um 14:07 Uhr

  • … wenn Sie sich den Assembler-Code zum Lesen von ausgerichtetem und nicht ausgerichtetem int ansehen, ist ausgerichtetes Lesen normalerweise eine einzelne mnemonische Zeichenfolge. Unausgerichtetes Lesen kann leicht 10 Montagezeilen umfassen, da es das int zusammensetzt, es Byte für Byte auswählt und an den richtigen Stellen des Registers platziert.

    – SF.

    12. Januar 2016 um 14:09 Uhr

  • @SF .: Es kann sein – aber selbst wenn dies nicht der Fall ist, lassen Sie sich nicht irreführen – auf einer x86-CPU (für ein offensichtliches Beispiel) werden die Operationen in Hardware ausgeführt, aber Sie erhalten immer noch ungefähr die gleichen Operationen und Verlangsamung.

    – Jerry Sarg

    12. Januar 2016 um 16:12 Uhr

Datenelemente (z. B. Mitglieder von Klassen und Strukturen) werden typischerweise an WORD- oder DWORD-Grenzen für Prozessoren der aktuellen Generation ausgerichtet, um die Zugriffszeiten zu verbessern. Das Abrufen eines DWORD an einer Adresse, die nicht durch 4 teilbar ist, erfordert mindestens einen zusätzlichen CPU-Zyklus auf einem 32-Bit-Prozessor. Also, wenn Sie zB drei Char-Mitglieder haben char a, b, c;neigen sie tatsächlich dazu, 6 oder 12 Byte Speicherplatz zu beanspruchen.

#pragma ermöglicht es Ihnen, dies zu überschreiben, um eine effizientere Speicherplatznutzung auf Kosten der Zugriffsgeschwindigkeit oder für die Konsistenz gespeicherter Daten zwischen verschiedenen Compilerzielen zu erreichen. Ich hatte viel Spaß bei diesem Übergang von 16-Bit- zu 32-Bit-Code; Ich gehe davon aus, dass die Portierung auf 64-Bit-Code bei einigen Codes die gleichen Kopfschmerzen verursachen wird.

Der Compiler könnte Mitglieder in Strukturen ausrichten, um eine maximale Leistung auf der bestimmten Plattform zu erreichen. #pragma pack Direktive können Sie diese Ausrichtung steuern. Normalerweise sollten Sie es für eine optimale Leistung standardmäßig belassen. Wenn Sie eine Struktur an den Remote-Computer übergeben müssen, werden Sie im Allgemeinen verwenden #pragma pack 1 um eine ungewollte Ausrichtung auszuschließen.

Benutzeravatar von Clifford
Clifford

Ein Compiler kann Platzieren Sie Strukturmitglieder auf bestimmten Byte-Grenzen aus Leistungsgründen auf einer bestimmten Architektur. Dies kann ungenutzte Auffüllungen zwischen Mitgliedern hinterlassen. Strukturpackung erzwingt zusammenhängende Mitglieder.

Dies kann beispielsweise wichtig sein, wenn Sie eine Struktur benötigen, die einem bestimmten Datei- oder Kommunikationsformat entspricht, in dem sich die Daten, die Sie benötigen, an bestimmten Positionen innerhalb einer Sequenz befinden müssen. Eine solche Verwendung befasst sich jedoch nicht mit Endianness-Problemen, sodass sie, obwohl sie verwendet wird, möglicherweise nicht portierbar ist.

Es kann auch die interne Registerstruktur eines E/A-Geräts wie beispielsweise eines UART- oder USB-Controllers genau überlagern, damit der Registerzugriff über eine Struktur und nicht über direkte Adressen erfolgt.

Benutzeravatar von stonemetal
Steinmetall

Ich habe Leute gesehen, die es verwenden, um sicherzustellen, dass eine Struktur eine ganze Cache-Zeile benötigt, um falsches Teilen in einem Multithread-Kontext zu verhindern. Wenn Sie eine große Anzahl von Objekten haben, die standardmäßig lose gepackt werden, könnte es Speicher sparen und die Cache-Leistung verbessern, um sie enger zu packen, obwohl ein nicht ausgerichteter Speicherzugriff die Dinge normalerweise verlangsamt, so dass es einen Nachteil geben kann.

1426620cookie-check#Pragmapack-Effekt

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy