Ci sono problemi di prestazioni quando si utilizza pragma pack (1)?

Le nostre intestazioni utilizzano #pragma pack(1) attorno alla maggior parte delle nostre strutture (utilizzate per I / O di rete e file). Comprendo che modifica l’allineamento delle strutture dal valore predefinito di 8 byte a un allineamento di 1 byte.

Supponendo che tutto sia eseguito in Linux a 32 bit (forse anche Windows), c’è qualche colpo di prestazioni che deriva da questo allineamento del pacco?

Non sono preoccupato della portabilità per le librerie, ma più della compatibilità di I / O di file e di rete con diversi pacchetti #pragma e problemi di prestazioni.

L’accesso alla memoria è più veloce quando può avvenire in corrispondenza di indirizzi di memoria allineati alle parole. L’esempio più semplice è la seguente struct (che ha utilizzato anche @Didier):

 struct sample { char a; int b; }; 

Di default, GCC inserisce padding, quindi a è a offset 0, e b è a offset 4 (allineamento di parole). Senza padding, b non è allineato alle parole e l’accesso è più lento.

Quanto più lento?

  • Per x86 a 32 bit, secondo il Manuale dello sviluppatore del software Intel 64 e IA32 Architectures :

    Il processore richiede due accessi alla memoria per creare un accesso alla memoria non allineato; gli accessi allineati richiedono solo un accesso alla memoria. Un operando word o doubleword che attraversa un limite di 4 byte o un operando quadword che attraversa un limite di 8 byte è considerato non allineato e richiede due cicli di bus di memoria separati per l’accesso.

    Come per la maggior parte delle domande sul rendimento, dovresti confrontare la tua applicazione per vedere quanto di un problema è nella pratica.

  • Secondo Wikipedia , le estensioni x86 come SSE2 richiedono l’ allineamento delle parole.
  • Molte altre architetture richiedono l’allineamento delle parole (e genereranno errori SIGBUS se le strutture dei dati non sono allineate alle parole).

Per quanto riguarda la portabilità: presumo che tu stia utilizzando #pragma pack(1) modo che tu possa inviare le strutture attraverso il cavo e da e verso il disco senza preoccuparti di compilatori o piattaforms diversi che comprimono le strutture in modo diverso. Questo è valido, tuttavia, ci sono un paio di problemi da tenere a mente:

  • Questo non fa nulla per gestire i problemi big endian e little endian. Puoi gestirli chiamando la famiglia di funzioni htons su qualsiasi int, unsigned, ecc. Nelle tue struct.
  • Nella mia esperienza, lavorare con strutture compilate e serializzabili nel codice dell’applicazione non è molto divertente. Sono molto difficili da modificare ed estendere senza compromettere la compatibilità con le versioni precedenti e, come già notato, ci sono delle penalizzazioni in termini di prestazioni. Prendi in considerazione il trasferimento dei contenuti delle tue strutture compressi e serializzabili in strutture equivalenti estese e non compresse per l’elaborazione, oppure considera l’utilizzo di una libreria di serializzazione a tutti gli effetti come Protocol Buffers (che ha binding C ).

Sì. Ci sono assolutamente.

Ad esempio, se si definisce una struttura:

 struct dumb { char c; int i; }; 

quindi ogni volta che si accede al membro i, la CPU viene rallentata, poiché il valore a 32 bit i non è accessibile in modo nativo, allineato. Per semplificare, immagina che la CPU debba ottenere 3 byte dalla memoria e quindi 1 altro byte dalla posizione successiva per trasferire il valore dalla memoria ai registri della CPU.

Quando si dichiara una struttura, la maggior parte dei compilatori inserisce i byte di riempimento tra i membri per assicurarsi che siano allineati agli indirizzi appropriati in memoria (in genere i byte di riempimento sono un multiplo delle dimensioni del testo). Ciò consente al compilatore di avere un accesso ottimizzato per accedere a questi membri.

#pragma pack(1) indica al compilatore di impacchettare i membri della struttura con un particolare allineamento. L’ 1 qui dice al compilatore di non inserire alcun padding tra i membri.

Quindi sì, c’è una penalità prestazionale definita , dal momento che costringi il compilatore a fare qualcosa oltre a ciò che farebbe naturalmente per l’ottimizzazione delle prestazioni. Inoltre, alcune piattaforms richiedono che gli oggetti siano allineati a specifici limiti e l’uso di strutture non livellate potrebbe darti dei difetti di segmentazione.

Idealmente, è meglio evitare di cambiare le regole di allineamento naturale predefinite. Ma se la direttiva ‘pragma pack’ non può essere affatto evitata (come nel tuo caso), allora lo schema di imballaggio originale deve essere ripristinato dopo la definizione delle strutture che richiedono un imballaggio stretto.

Ad esempio:

 //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) 

Dipende dall’architettura sottostante e dal modo in cui gestisce gli indirizzi non allineati.

x86 gestisce gli indirizzi non allineati con garbo, anche se a un costo prestazionale, mentre altre architetture come ARM possono richiamare un errore di allineamento ( SIGBUS ) o persino “arrotondare” l’indirizzo disallineato al limite più vicino, nel qual caso il codice fallirà in modo orribile modo.

La linea di fondo è, compila solo se sei sicuro che l’architettura sottostante gestirà gli indirizzi non allineati e se il costo di I / O di rete è superiore al costo di elaborazione.

Tecnicamente, sì, influenzerebbe le prestazioni, ma solo per quanto riguarda l’elaborazione interna. Se hai bisogno di strutture confezionate per rete / file IO, c’è un equilibrio tra i requisiti impacchettati e l’elaborazione interna. Per elaborazione interna, intendo, il lavoro che fai sui dati tra l’IO. Se fai pochissima elaborazione, non perderai molto in termini di prestazioni. Altrimenti, potresti voler eseguire l’elaborazione interna su strutture correttamente allineate e solo “impacchettare” i risultati quando fai l’IO. In alternativa, è ansible passare all’utilizzo di sole strutture allineate predefinite, ma è necessario assicurarsi che tutte le persone siano allineate allo stesso modo (client di rete e file).

Esistono alcune istruzioni del codice macchina che funzionano su 32 bit o 64 bit (o anche più) ma si aspettano che i dati siano allineati sugli indirizzi di memoria. Se non lo sono, devono fare più di una lettura / scrittura sulla memoria per eseguire il loro compito. Quanto poco quel colpo di prestazioni dipende pesantemente da ciò che stai facendo con i dati. Se costruisci grandi matrici di strutture ed esegui calcoli estesi su di esse, potrebbe diventare grande. Ma se si memorizzano i dati solo una volta solo per leggerli in un altro momento, convertendoli comunque in un stream di byte, allora potrebbe essere appena percettibile.