È buona norma utilizzare tipi di dati più piccoli per le variabili per risparmiare memoria?

29

Quando ho imparato il linguaggio C ++ per la prima volta ho imparato che oltre a int, float ecc, esistevano versioni più piccole o più grandi di questi tipi di dati all'interno del linguaggio. Ad esempio potrei chiamare una variabile x

int x;
or 
short int x;

La differenza principale è che int breve richiede 2 byte di memoria mentre int richiede 4 byte e short int ha un valore minore, ma potremmo anche chiamarlo per renderlo ancora più piccolo:

int x;
short int x;
unsigned short int x;

che è ancora più restrittivo.

La mia domanda qui è se è una buona pratica utilizzare tipi di dati separati in base ai valori che la variabile assume all'interno del programma. È una buona idea dichiarare sempre le variabili in base a questi tipi di dati?

    
posta Bugster 17.04.2012 - 09:05
fonte

8 risposte

39

La maggior parte delle volte il costo dello spazio è trascurabile e non dovresti preoccuparti di questo, tuttavia dovresti preoccuparti delle informazioni extra che stai dando dichiarando un tipo. Ad esempio, se tu:

unsigned int salary;

Stai fornendo utili informazioni a un altro sviluppatore: lo stipendio non può essere negativo.

La differenza tra breve, int, lungo raramente causerà problemi di spazio nell'applicazione. È più probabile che tu accetti involontariamente il falso presupposto che un numero si adatta sempre ad alcuni tipi di dati. Probabilmente è più sicuro usare sempre int a meno che non sei sicuro al 100% che i tuoi numeri saranno sempre molto piccoli. Anche in questo caso, è improbabile che ti risparmi una quantità notevole di spazio.

    
risposta data 17.04.2012 - 09:11
fonte
26

L'OP non ha detto nulla riguardo al tipo di sistema per cui stanno scrivendo programmi, ma presumo che l'OP stia pensando a un tipico PC con memoria di GB, dal momento che si parla di C ++. Come dice uno dei commenti, anche con quel tipo di memoria, se hai diversi milioni di elementi di un tipo - come un array - allora la dimensione della variabile può fare la differenza.

Se entri nel mondo dei sistemi embedded, che non è davvero al di fuori della portata della domanda, dal momento che l'OP non lo limita ai PC, la dimensione dei tipi di dati è molto importante. Ho appena terminato un rapido progetto su un microcontrollore a 8 bit che ha solo 8K parole di memoria di programma e 368 byte di RAM. Lì, ovviamente ogni byte conta. Non si utilizza mai una variabile più grande del necessario (sia dal punto di vista dello spazio che della dimensione del codice: i processori a 8 bit utilizzano molte istruzioni per manipolare i dati a 16 e 32 bit). Perché usare una CPU con risorse così limitate? In grandi quantità, possono costare solo un quarto.

Attualmente sto facendo un altro progetto embedded con un microcontrollore basato su MIPS a 32 bit che ha 512K byte di flash e 128K byte di RAM (e costa circa $ 6 in quantità). Come con un PC, la dimensione dei dati "naturali" è di 32 bit. Ora diventa più efficiente, in termini di codice, utilizzare gli inte per la maggior parte delle variabili invece di caratteri o cortometraggi. Ma ancora una volta, qualsiasi tipo di array o struttura deve essere considerato se i tipi di dati più piccoli sono garantiti. A differenza dei compilatori per sistemi più grandi, è più probabile che le variabili in una struttura siano imballate su un sistema embedded. Mi preoccupo di provare sempre a mettere prima tutte le variabili a 32 bit, poi a 16 bit, quindi a 8 bit per evitare eventuali "buchi".

    
risposta data 17.04.2012 - 15:58
fonte
11

La risposta dipende dal tuo sistema. In generale, ecco i vantaggi e gli svantaggi dell'utilizzo di tipi più piccoli:

I vantaggi

  • I tipi più piccoli utilizzano meno memoria sulla maggior parte dei sistemi.
  • I tipi più piccoli forniscono calcoli più veloci su alcuni sistemi. Particolarmente vero per float vs double su molti sistemi. E i tipi int più piccoli forniscono anche un codice significativamente più veloce su CPU a 8 o 16 bit.

Svantaggi

  • Molte CPU hanno requisiti di allineamento. Alcuni dati di allineamento di accesso sono più veloci di quelli non allineati. Alcuni devono avere i dati allineati per poterli accedere. I tipi interi più grandi equivalgono a un'unità allineata, quindi è probabile che non siano disallineati. Ciò significa che il compilatore potrebbe essere costretto a inserire gli interi più piccoli in quelli più grandi. E se i tipi più piccoli fanno parte di una struttura più grande, è possibile ottenere vari byte di riempimento inseriti silenziosamente in qualsiasi punto della struttura dal compilatore, per correggere l'allineamento.
  • Conversioni implicite pericolose. C e C ++ hanno regole oscure e pericolose per come le variabili sono promosse a quelle più grandi, implicitamente senza un typecast. Esistono due serie di regole di conversione implicite intrecciate tra loro, denominate "regole di promozione intera" e le "normali conversioni aritmetiche". Leggi di più su di loro qui . Queste regole sono una delle cause più comuni di bug in C e C ++. Puoi evitare un sacco di problemi semplicemente usando lo stesso tipo intero in tutto il programma.

Il mio consiglio è di fare questo:

system                             int types

small/low level embedded system    stdint.h with smaller types
32-bit embedded system             stdint.h, stick to int32_t and uint32_t.
32-bit desktop system              Only use (unsigned) int and long long.
64-bit system                      Only use (unsigned) int and long long.

In alternativa, puoi usare int_leastn_t o int_fastn_t da stdint.h, dove n è il numero 8, 16, 32 o 64. Il tipo int_leastn_t significa "Voglio che questo sia almeno n byte ma non mi interessa se il compilatore lo alloca come un tipo più grande per adattarsi all'allineamento ".

int_fastn_t significa "Voglio che questo sia n byte a lungo, ma se renderà il mio codice più veloce, il compilatore dovrebbe usare un tipo più grande di quanto specificato".

Generalmente, i vari tipi di stdint.h sono di gran lunga migliori rispetto al semplice int ecc, perché sono portatili. L'intenzione con int era di non dargli una larghezza specificata solo per renderla portabile. Ma in realtà è difficile portarlo perché non sai mai quanto sarà grande su un sistema specifico.

    
risposta data 20.04.2012 - 16:05
fonte
10

A seconda di come funziona il sistema operativo specifico, generalmente ci si aspetta che la memoria venga allocata in modo non ottimizzato in modo tale che quando si chiama un byte o una parola o qualche altro tipo di dati piccoli da allocare, il valore occupa un intero registro tutto di esso è molto personale. Il modo in cui il compilatore o l'interprete lavora per interpretarlo è un'altra cosa, quindi se dovessi compilare un programma in C #, ad esempio, il valore potrebbe occupare fisicamente un registro per se stesso, tuttavia il valore verrà controllato per garantire che non lo faccia prova a memorizzare un valore che superi i limiti del tipo di dati desiderato.

Per quanto riguarda le prestazioni, e se sei veramente pignolo su queste cose, è probabile che sia più veloce usare semplicemente il tipo di dati che più si avvicina alle dimensioni del registro di destinazione, ma poi ti perdi tutto quel delizioso zucchero sintattico che fa lavorare con variabili così facili.

In che modo ti aiuta? Bene, dipende da te decidere quale tipo di situazione stai codificando. Per quasi tutti i programmi che ho scritto, basta affidarsi al compilatore per ottimizzare le cose e utilizzare il tipo di dati che è più utile per te. Se hai bisogno di alta precisione, usa i tipi di dati a virgola mobile più grandi. Se lavori con solo valori positivi, puoi probabilmente usare un numero intero senza segno, ma per la maggior parte, è sufficiente usare semplicemente il tipo di dati int.

Se tuttavia si hanno requisiti di dati molto rigidi, come la scrittura di un protocollo di comunicazione o un qualche tipo di algoritmo di crittografia, usare i datatypes controllati dal range può essere molto utile, specialmente se si tenta di evitare problemi relativi ai dati overruns / underruns o valori di dati non validi.

L'unica altra ragione per cui riesco a pensare in modo ineccepibile all'uso di tipi di dati specifici è quando stai cercando di comunicare l'intento all'interno del tuo codice. Ad esempio, se utilizzi un cortocircuito, stai dicendo ad altri sviluppatori che stai consentendo numeri positivi e negativi all'interno di un intervallo di valori molto piccolo.

    
risposta data 17.04.2012 - 09:26
fonte
6

Poiché scarfridge ha commentato, questo è un

Classic case of premature optimization.

Il tentativo di ottimizzare l'utilizzo della memoria potrebbe influire su altre aree di rendimento e le regole d'oro di l'ottimizzazione è:

The First Rule of Program Optimisation: Don't do it.

The Second Rule of Program Optimisation (for experts only!): Don't do it yet."

— Michael A. Jackson

Per sapere se ora è il momento di ottimizzare richiede benchmark e test. Devi sapere dove il tuo codice è inefficiente, in modo che tu possa indirizzare le tue ottimizzazioni.

Per determinare se la versione ottimizzata del codice è effettivamente migliore dell'implementazione ingenua in un dato momento, è necessario confrontarli fianco a fianco con gli stessi dati.

Inoltre, ricorda che solo perché una determinata implementazione è più efficiente nell'attuale generazione di CPU, non significa che sempre sia così. La mia risposta alla domanda La micro-ottimizzazione è importante per la codifica? fornisce un esempio dell'esperienza personale in cui un'ottimizzazione obsoleta ha comportato un rallentamento dell'ordine di grandezza.

Su molti processori, gli accessi di memoria non allineati sono significativamente più costosi rispetto agli accessi di memoria allineati. Imballare un paio di cortometraggi nella tua struttura può solo significare che il tuo programma deve eseguire l'operazione di pacchettizzazione / decompressione ogni volta tocchi entrambi i valori.

Per questo motivo, i compilatori moderni ignorano i tuoi suggerimenti. Come nikie commenti:

With standard packing/alignment compiler settings, the variables will be aligned to 4 byte boundaries anyway, so there might not by any difference at all.

Secondo indovina il tuo compilatore a tuo rischio e pericolo.

C'è spazio per tali ottimizzazioni, quando si lavora con dataset di terabyte o microcontroller incorporati, ma per la maggior parte di noi non è una vera preoccupazione.

    
risposta data 17.04.2012 - 16:35
fonte
3

The main difference being that short int takes 2 bytes of memory while int takes 4 bytes, and short int has a lesser value, but we could also call this to make it even smaller:

Questo non è corretto. Non puoi fare ipotesi sul numero di byte che ogni tipo contiene, tranne che char è un byte e almeno 8 bit per byte, insieme alla dimensione di ciascun tipo che è maggiore o uguale alla precedente.

I vantaggi in termini di prestazioni sono incredibilmente minuscoli per le variabili stack: probabilmente saranno allineati / riempiti comunque.

Per questo motivo al giorno d'oggi short e long non sono praticamente utilizzabili, e quasi sempre preferisci usare int .

Ovviamente, c'è anche stdint.h che va perfettamente bene quando int non lo taglia. Se si assegna mai array enormi di numeri interi / structs, allora un intX_t ha senso in quanto si può essere efficienti e fare affidamento sulla dimensione del tipo. Questo non è affatto prematuro in quanto è possibile salvare megabyte di memoria.

    
risposta data 17.04.2012 - 15:57
fonte
3

Ciò avverrà da un tipo di OOP e / o da un punto di vista aziendale / applicativo e potrebbe non essere applicabile in determinati campi / domini, ma mi piacerebbe far apparire il concetto di ossessione primitiva .

È una buona idea usare diversi tipi di dati per diversi tipi di informazioni nella tua applicazione. Tuttavia, probabilmente non è una buona idea usare i tipi built-in per questo, a meno che tu non abbia dei seri problemi di prestazioni (che sono stati misurati e verificati e così via).

Se vogliamo modellare le temperature in Kelvin nella nostra applicazione, POTREBBE usare un ushort o uint o qualcosa di simile per indicare che "la nozione di gradi negativi Kelvin è assurda e un errore logico di dominio". L'idea alla base di questo è sana, ma non stai andando fino in fondo. Quello che abbiamo capito è che non possiamo avere valori negativi, quindi è utile se riusciamo a ottenere il compilatore per assicurarci che nessuno assegni un valore negativo a una temperatura Kelvin. È anche vero che non è possibile eseguire operazioni bit a bit sulle temperature. E non è possibile aggiungere una misura di peso (kg) a una temperatura (K). Ma se modellate sia la temperatura che la massa come uint s, possiamo fare proprio questo.

L'uso di tipi built-in per modellare le nostre entità DOMAIN è destinato a portare a qualche codice disordinato e alcuni assegni falliti e invarianti infranti. Anche se un tipo cattura QUALCHE parte dell'entità (non può essere negativa), è destinata a perdere altre (non può essere usata in espressioni aritmetiche arbitrarie, non può essere trattata come una matrice di bit, ecc.)

La soluzione è definire nuovi tipi che incapsulino gli invarianti. In questo modo puoi essere sicuro che il denaro è denaro e le distanze sono distanze, e non puoi aggiungerle insieme e non puoi creare una distanza negativa, ma puoi creare una somma di denaro negativa (o un debito). Ovviamente, questi tipi useranno internamente i tipi incorporati, ma questo è nascosto dai client. In relazione alla tua domanda sul consumo di prestazioni / memoria, questo tipo di cose può permetterti di cambiare il modo in cui le cose vengono archiviate internamente senza cambiare l'interfaccia delle tue funzioni che operano sulle entità di dominio, dovresti scoprire che dannazione, un short è semplicemente troppo dannatamente grande.

    
risposta data 16.07.2016 - 16:26
fonte
1

Sì, certo. È una buona idea usare uint_least8_t per dizionari, enormi array di costanti, buffer, ecc. È meglio usare uint_fast8_t per scopi di elaborazione.

uint8_least_t (spazio di archiviazione) - > uint8_fast_t (elaborazione) - > uint8_least_t (spazio di archiviazione).

Ad esempio stai prendendo il simbolo di 8 bit da source , i codici a 16 bit da dictionaries e alcuni 32 bit constants . Di quanto stai elaborando operazioni a 10-15 bit con loro e output 8 bit destination .

Immaginiamo che devi elaborare 2 gigabyte di source . La quantità di operazioni bit è enorme. Riceverai un grande bonus di perfomance se passerai ai tipi veloci durante l'elaborazione. I tipi veloci possono essere diversi per ogni famiglia di CPU. Puoi includere stdint.h e utilizzare uint_fast8_t , uint_fast16_t , uint_fast32_t , ecc.

Potresti usare uint_least8_t invece di uint8_t per la portabilità. Ma nessuno sa davvero quale CPU moderna userà questo caratteristica. La macchina VAC è un pezzo da museo. Quindi forse è eccessivo.

    
risposta data 08.12.2018 - 12:04
fonte

Leggi altre domande sui tag