Domanda:
Consigli per l'utilizzo di campi di bit in Structs
anthropomo
2014-10-01 05:19:12 UTC
view on stackexchange narkive permalink

Devo tenere traccia di una grande quantità di dati (per un Arduino) in un programma mentre mi occupo di una discreta quantità di altre attività.

Ho iniziato con una struttura come questa:

  struct MyStruct {// nota: questi nomi potrebbero anche essere foo bar baz uint8_t color; stato booleano; uint8_t area; uint8_t numero; uint8_t len;};  

Ho un array di 800 di questi. A 5 byte ciascuno, è 4Kb, o metà della ram su Arduino Mega.

Ho cambiato in una struttura come questa:

  struct MyStruct {uint8_t color: 3; // valore massimo 7 // uint8_t state: 1; *** commosso, grazie Ignacio *** uint8_t area: 5; // m.v. 31 uint8_t numero; uint8_t len: 5; // m.v. 31 uint8_t state: 1; // m.v. 1};  

Capisco che questo riduce la dimensione di ogni istanza a 3 byte, per un totale di 2,4 KB nel mio array.

L'ho letto in alcuni implementazioni / chipset che utilizzano campi di bit nelle strutture possono portare a un'esecuzione meno efficiente. Ovviamente questo è un uso più efficiente della memoria, ma la mia domanda è:

In che modo Arduino gestisce i campi di bit? Sarà migliore, peggiore o trascurabilmente diverso in termini o velocità durante l'iterazione attraverso una matrice di strutture di campi di bit? C'è un buon modo per testarlo?

Quell'ordine utilizzerà 4 byte. Prendi quel campo da 1 bit tra gli altri 8.
@Ignacio Incredibile ... hai appena ridotto di un altro 9% il mio utilizzo totale della memoria.
Il motivo per cui i campi di bit nelle strutture possono portare a un'esecuzione meno efficiente è a causa delle restrizioni sull'allineamento delle parole. Per esempio. se incrociano il naturale allineamento 32/64, provocando accessi multipli. Questo non dovrebbe essere un problema per ATmega
La domanda che devi porsi è se sei più preoccupato di rimanere senza RAM, memoria del programma o cicli della CPU: questa è una situazione in cui puoi ottimizzare un po 'per uno al prezzo di un altro e un certo impatto sul terzo. A un certo punto dovresti anche chiederti se un Arduino basato su ATmega è davvero la piattaforma giusta.
Sono stato in grado di ristrutturare il programma per utilizzare l'indice dell'array in cui stavo usando il numero a 8 bit, quindi fino a 2 byte per struttura, un altro 9% rasato. Sembra che io possa risparmiare i cicli. Io itero attraverso gli 800 struct ogni 50 millisecondi mentre gestisco anche richieste http intermittenti. Nessun problema. Ho spinto i limiti di ATmega con questo, ma mi sembra di essere ben al di sotto.
Ecco alcune ulteriori letture che mi sono imbattuto in seguito: http://www.catb.org/esr/structure-packing/
Tre risposte:
#1
+8
jdr5ca
2014-10-01 13:04:39 UTC
view on stackexchange narkive permalink

Quello che stai affrontando è il classico compromesso tra memoria e tempo. I campi di bit saranno più piccoli nella memoria, ma richiederà più tempo per operare. Puoi contare che, indipendentemente dal processore, i campi di bit saranno più lenti.

Usi la parola efficiente , ma quella parola non ha alcun significato senza una metrica per ciò che è buono o male. Quando hai solo 8 k di RAM, l'uso della memoria è pessimo, il tempo potrebbe essere economico. Se hai vincoli di tempo reale, usare il tempo è un male e la memoria potrebbe essere economica. In generale, puoi solo comprare la tua via d'uscita da questo compromesso. In altre parole, quando trovi sia il tempo che la memoria pessimi, spendi denaro e usa un chip più grande. Non esiste un'unica risposta per ciò che è buono o cattivo. Questo fa parte del motivo per cui ci sono così tante scelte per i microcontrollori, le persone adattano il chip all'applicazione e l'applicazione al chip.

Il popolamento dei bit di un campo bit sarà più lento del riempimento di byte completi. Prendiamo ad esempio

  x = 5; ... asimplestruct.len = x; // vsabitfield.len = x;  

Il primo caso semplice sarà semplicemente:

  • carica il valore di x in un registro
  • memorizzalo nel byte per len

Il secondo fa qualcosa di simile:

  • carica il valore corrente di abitfield
  • carica una maschera
  • cancella i bit per len
  • carica il valore corrente di x
  • carica una maschera
  • cancella i bit di x inutilizzati
  • sposta i bit di x
  • o le x con abitfield
  • salva il valore corrente in memoria

Se tutte le tue operazioni stanno comprimendo i dati nel campo bit, o decomprimendoli fuori dal campo bit, dovresti aspettarti un'esecuzione più lenta. I campi di bit sono un tipo di compressione: costano tick.

Ma spostarsi nei campi di bit sarà più veloce perché ci sono meno byte da caricare e memorizzare. Se stai ordinando questo array, il numero di byte più piccolo potrebbe essere un vantaggio. Se li stai trasferendo su una porta seriale, la dimensione compressa del campo di bit potrebbe essere vincente.

Quindi, rispetto alla tua domanda:

C'è un buon modo per testarlo?

L'unico buon modo per testarlo è scrivere casi di test per entrambi gli approcci utilizzando un modello che corrisponde strettamente alla tua applicazione. È davvero importante quale combinazione di operazioni esegui per decidere se la differenza è trascurabile o significativa.

Quando esegui questo tipo di esperimento di ottimizzazione, usa sicuramente il controllo del codice sorgente sul tuo progetto. Puoi creare un repository GIT o Mercurial locale con pochi clic. Mantenere una catena di checkpoint ti consente di strappare il tuo codice esplorando gli effetti di diverse implementazioni. Se prendi una svolta sbagliata, il repository ti consente semplicemente di tornare all'ultimo punto buono e provare un altro percorso.

(Nota a margine: questa volta il compromesso della memoria esiste anche nella direzione opposta. attraverso le opzioni del compilatore per i processori di classe desktop, troverai qualcosa chiamato impacchettamento della struttura . Questa opzione ti consente di aggiungere byte vuoti tra i campi a byte singolo in modo che rimangano allineati ai confini di parola o doppia parola. sembra una follia buttare via la RAM di proposito, ma su processori con registri e bus di 16 o 32 bit, le cosiddette operazioni di memoria allineate con parole o parole possono essere più veloci delle operazioni basate sui byte.)

#2
+3
JRobert
2014-10-01 22:23:53 UTC
view on stackexchange narkive permalink

Una discussione sui campi di bit non sarebbe completa senza menzionare che:

Le strutture con campi di bit possono essere usate come un modo portatile per tentare di ridurre la memoria richiesta per una struttura (con il probabile costo di aumentare lo spazio di istruzione e il tempo necessari per accedere ai campi), o come un modo non portabile per descrivere un layout di archiviazione noto a livello di bit.

[Kernighan & Ritchie, The C Programming Language, seconda edizione , Appendice A-8.3]

Altri poster, incluso te stesso, hanno affrontato il compromesso spazio / tempo. Ma ciò che a volte manca (forse non è rilevante per la tua applicazione -?) È la completa dipendenza dall'implementazione del layout di archiviazione . Ciò è importante quando le applicazioni condividono questi dati con un altro processo: lettura o scrittura di hardware, le cui assegnazioni dei bit di registro sono necessariamente fisse; comunicare i dati a un altro sistema (tramite rete o dispositivo di archiviazione non importa); o ad un'altra applicazione sullo stesso sistema, che potrebbe essere stata compilata con un compilatore o una versione del compilatore differente.

#3
+1
soerium
2014-10-01 16:26:33 UTC
view on stackexchange narkive permalink

Un piccolo suggerimento: vedo che la struttura non è allineata 1B per CPU a 8 bit. Alla fine della tua struttura puoi aggiungere padding 2bit di lunghezza o ripensare all'aumento della dimensione di state”.

  struct MyStruct {uint8_t color : 3; // valore massimo 7 // uint8_t state: 1; *** commosso, grazie Ignacio *** uint8_t area: 5; // m.v. 31 uint8_t numero; uint8_t len: 5; // m.v. 31 uint8_t state: 1; // m.v. 1 uint8_t padding: 2;};  
L'aggiunta del riempimento non modifica la memoria dinamica in fase di compilazione. Perdona la mia ignoranza qui, ma sembra che il compilatore si occupi del riempimento. O sta succedendo qualcosa in fase di esecuzione per compensare se non includo il riempimento?
È vero. In questo caso è più come una buona pratica.


Questa domanda e risposta è stata tradotta automaticamente dalla lingua inglese. Il contenuto originale è disponibile su stackexchange, che ringraziamo per la licenza cc by-sa 3.0 con cui è distribuito.
Loading...