Programmazione C ed efficienza della variabile error_code
-
11-09-2019 - |
Domanda
La maggior parte del codice che abbia mai letto utilizza a int
per la gestione degli errori standard (valori restituiti da funzioni e simili).Ma mi chiedo se si possa trarre qualche vantaggio dall'utilizzo di a uint_8
sarà un compilatore -- leggi:la maggior parte dei compilatori C sulla maggior parte delle architetture - produce istruzioni utilizzando la modalità indirizzo immediato - ovvero incorpora l'intero da 1 byte nell'istruzione ?L'istruzione chiave a cui sto pensando è il confronto dopo che una funzione, utilizzando uint_8 come tipo restituito, restituisce.
Potrei pensare alle cose in modo errato, poiché l'introduzione di un tipo da 1 byte causa solo problemi di allineamento - probabilmente c'è una ragione perfettamente sensata per cui le compilazioni amano comprimere le cose in 4 byte e questo è forse il motivo per cui tutti usano solo int - e poiché si tratta di un problema relativo allo stack piuttosto che all'heap, non vi è alcun sovraccarico reale.
Fare la cosa giusta è ciò a cui sto pensando.Ma diciamo, per amor di discussione, che si tratta di un popolare microprocessore economico per un orologio intelligente e che è configurato con 1k di memoria ma ha diverse modalità di indirizzamento nel suo set di istruzioni: D
Un'altra domanda per specializzare leggermente la discussione (x86) sarebbe:è il letterale in:
uint_32 x=func(); x==1;
E
uint_8 x=func(); x==1;
dello stesso tipo?oppure il compilatore genererà un valore letterale di 8 byte nel secondo caso.Se è così, può usarlo per generare un'istruzione di confronto che ha il letterale come valore immediato e l'int restituito come riferimento al registro. Vedere Tipi di istruzioni CMP..
Soluzione
Ecco cosa farà un particolare compilatore per il seguente codice:
extern int foo(void) ;
void bar(void)
{
if(foo() == 31) { //error code 31
do_something();
} else {
do_somehing_else();
}
}
0: 55 push %ebp
1: 89 e5 mov %esp,%ebp
3: 83 ec 08 sub $0x8,%esp
6: e8 fc ff ff ff call 7 <bar+0x7>
b: 83 f8 1f cmp $0x1f,%eax
e: 74 08 je 18 <bar+0x18>
10: c9 leave
11: e9 fc ff ff ff jmp 12 <bar+0x12>
16: 89 f6 mov %esi,%esi
18: c9 leave
19: e9 fc ff ff ff jmp 1a <bar+0x1a>
un'istruzione da 3 byte per cmp.Se foo () restituisce un carattere, otteniamo b:3c 1f cmp $0x1f,%al
Se cerchi l'efficienza però.Non dare per scontato che il confronto delle cose in %a1 sia più veloce del confronto con %eax
Altri suggerimenti
Potrebbero esserci differenze di velocità molto piccole tra i diversi tipi di integrali su una particolare architettura.Ma non puoi fare affidamento su di esso, potrebbe cambiare se passi a un hardware diverso e potrebbe anche funzionare più lentamente se esegui l'aggiornamento a un hardware più recente.
E se parli di x86 nell'esempio che stai fornendo, fai un falso presupposto:Un bisogno immediato deve essere del tipo uint8_t
.
In realtà gli immediati a 8 bit incorporati nell'istruzione sono di tipo int8_t
e può essere utilizzato con byte, parole, dwords e qwords, in notazione C: char
, short
, int
E long long
.
Quindi su questa architettura non ci sarebbe alcun vantaggio, né nella dimensione del codice né nella velocità di esecuzione.
Dovresti utilizzare i tipi int o unsigned int per i tuoi calcoli.Utilizzo di tipi più piccoli solo per i composti (strutture/array).La ragione di ciò è che int è normalmente definito come il tipo integrale "più naturale" per il processore, tutti gli altri tipi derivati potrebbero richiedere l'elaborazione per funzionare correttamente.Nel nostro progetto compilato con gcc su Solaris per SPARC il caso che accede a variabili a 8 e 16 bit aggiunge un'istruzione al codice.Quando si caricava un tipo più piccolo dalla memoria era necessario assicurarsi che la parte superiore del registro fosse impostata correttamente (estensione del segno per il tipo con segno o 0 per senza segno).Ciò ha reso il codice più lungo e ha aumentato la pressione sui registri, deteriorando le altre ottimizzazioni.
Faccio un esempio concreto:
Ho dichiarato due variabili di una struttura come uint8_t e ho ottenuto quel codice in Sparc Asm:
if(p->BQ > p->AQ)
è stato tradotto in
ldub [%l1+165], %o5 ! <variable>.BQ,
ldub [%l1+166], %g5 ! <variable>.AQ,
and %o5, 0xff, %g4 ! <variable>.BQ, <variable>.BQ
and %g5, 0xff, %l0 ! <variable>.AQ, <variable>.AQ
cmp %g4, %l0 ! <variable>.BQ, <variable>.AQ
bleu,a,pt %icc, .LL586 !
Ed ecco cosa ho ottenuto quando ho dichiarato le due variabili come uint_t
lduw [%l1+168], %g1 ! <variable>.BQ,
lduw [%l1+172], %g4 ! <variable>.AQ,
cmp %g1, %g4 ! <variable>.BQ, <variable>.AQ
bleu,a,pt %icc, .LL587 !
Due operazioni aritmetiche in meno e 2 registri in più per altre cose
Ai processori in genere piace lavorare con le dimensioni del registro naturale, che in C è "int".
Anche se ci sono delle eccezioni, stai pensando troppo a un problema che non esiste.