Domanda

Su una CPU a 32 bit, un numero intero è 4 byte e un numero intero breve è 2 byte. Se sto scrivendo un'applicazione C / C ++ che utilizza molti valori numerici che rientrano sempre nell'intervallo fornito di un numero intero breve, è più efficiente utilizzare numeri interi a 4 byte o numeri interi a 2 byte?

Ho sentito suggerire che numeri interi a 4 byte sono più efficienti in quanto si adatta alla larghezza di banda del bus dalla memoria alla CPU. Tuttavia, se sto sommando due numeri interi brevi, la CPU impacchetterebbe entrambi i valori in un unico passaggio in parallelo (estendendo così la larghezza di banda a 4 byte del bus)?

È stato utile?

Soluzione

Sì, dovresti assolutamente utilizzare un numero intero a 32 bit su una CPU a 32 bit, altrimenti potrebbe finire per mascherare i bit inutilizzati (ad esempio, farà sempre i calcoli in 32 bit, quindi convertirà la risposta in 16 bit)

Non eseguirà due operazioni a 16 bit contemporaneamente per te, ma se scrivi il codice da solo e sei sicuro che non trabocchi, puoi farlo da solo.

Modifica : dovrei aggiungere che dipende in qualche modo anche dalla tua definizione di "efficiente". Mentre sarà in grado di eseguire operazioni a 32 bit più rapidamente, ovviamente utilizzerai il doppio della memoria.

Se questi vengono utilizzati per calcoli intermedi in un loop interno da qualche parte, utilizzare 32 bit. Se, tuttavia, stai leggendo questo dal disco, o anche se devi solo pagare per un errore nella cache, potrebbe ancora funzionare meglio usare numeri interi a 16 bit. Come per tutte le ottimizzazioni, c'è solo un modo per sapere: profilalo .

Altri suggerimenti

Se hai una vasta gamma di numeri, scegli la dimensione più piccola che funzioni. Sarà più efficiente lavorare con un array di 16 bit corti rispetto a 32 bit in quanto si ottiene una densità della cache doppia. Il costo di qualsiasi estensione di segno che la CPU deve fare per lavorare con valori a 16 bit nei registri a 32 bit è banalmente trascurabile rispetto al costo di una mancanza di cache.

Se stai semplicemente usando le variabili membro in classi mescolate con altri tipi di dati, allora è meno chiaro poiché i requisiti di riempimento probabilmente rimuoveranno qualsiasi vantaggio di risparmio di spazio dei valori di 16 bit.

Se stai utilizzando " molti " valori interi, il collo di bottiglia nell'elaborazione potrebbe essere la larghezza di banda in memoria. Gli interi a 16 bit si comprimono più strettamente nella cache dei dati e sarebbero quindi una vittoria delle prestazioni.

Se stai eseguendo una compressione dei numeri su una grande quantità di dati, dovresti leggere Cosa dovrebbe fare ogni programmatore Conoscere la memoria di Ulrich Drepper. Concentrati sul capitolo 6, sulla massimizzazione dell'efficienza della cache di dati.

Una CPU a 32 bit è una CPU che di solito funziona internamente su valori a 32 bit, ma ciò non significa che sia più lenta quando si esegue la stessa operazione su un valore di 8/16 bit. x86 per esempio, ancora compatibile all'indietro fino all'8086, può operare su frazioni di un registro. Ciò significa che anche se un registro ha una larghezza di 32 bit, può funzionare solo sui primi 16 o sui primi 8 bit di quel registro e non ci sarà alcun rallentamento. Questo concetto è stato persino adottato da x86_64, dove i registri sono a 64 bit, ma possono ancora funzionare solo sui primi 32, 16 o 8 bit.

Anche le CPU x86 caricano sempre un'intera riga della cache dalla memoria, se non già nella cache, e una riga della cache è comunque maggiore di 4 byte (per CPU a 32 bit anziché 8 o 16 byte) e quindi il caricamento di 2 byte dalla memoria è altrettanto veloce del caricamento di 4 byte dalla memoria. Se si elaborano molti valori dalla memoria, i valori a 16 bit potrebbero effettivamente essere molto più veloci dei valori a 32 bit, poiché ci sono meno trasferimenti di memoria. Se una riga della cache è di 8 byte, ci sono quattro valori a 16 bit per riga di cache, ma solo due valori a 32 bit, quindi quando si usano 16 bit si ha un accesso alla memoria ogni quattro valori, usando 32 bit si ha uno ogni due valori , con conseguente doppio trasferimento per l'elaborazione di un array int di grandi dimensioni.

Altre CPU, come ad esempio PPC, non possono elaborare solo una frazione di un registro, ma elaborano sempre l'intero registro. Tuttavia, queste CPU di solito hanno operazioni di caricamento speciali che consentono loro, ad es. caricare un valore di 16 bit dalla memoria, espanderlo a 32 bit e scriverlo in un registro. Successivamente hanno una speciale operazione di memorizzazione che prende il valore dal registro e memorizza solo gli ultimi 16 bit in memoria; entrambe le operazioni richiedono solo un ciclo della CPU, proprio come richiederebbe un caricamento / archivio a 32 bit, quindi non vi è alcuna differenza di velocità. E poiché PPC può eseguire solo operazioni aritmetiche sui registri (a differenza di x86, che può anche operare direttamente sulla memoria), questa procedura di caricamento / archiviazione si svolge comunque se si utilizzano 32 bit o 16 bit.

L'unico svantaggio, se si eseguono la catena di più operazioni su una CPU a 32 bit che può operare solo su registri completi, è che il risultato a 32 bit dell'ultima operazione potrebbe dover essere "ridotto". a 16 bit prima dell'esecuzione dell'operazione successiva, altrimenti il ??risultato potrebbe non essere corretto. Un tale taglio è solo un singolo ciclo della CPU, tuttavia (una semplice operazione AND), e i compilatori sono molto bravi a capire quando un tale taglio è davvero necessario e quando lo si lascia fuori non avrà alcuna influenza sul risultato finale , quindi un tale taglio non viene eseguito dopo ogni istruzione, ma viene eseguito solo se davvero inevitabile. Alcune CPU offrono vari "migliorati" istruzioni che rendono superfluo un tale taglio e ho visto un sacco di codice nella mia vita, dove mi aspettavo un tale taglio, ma guardando il codice assembly generato, il compilatore ha trovato un modo per evitarlo del tutto.

Quindi, se ti aspetti una regola generale qui, dovrò deluderti. Né si può dire con certezza che le operazioni a 16 bit siano ugualmente veloci a quelle a 32 bit, né si può dire con certezza che le operazioni a 32 bit saranno sempre più veloci. Dipende anche cosa sta facendo esattamente il tuo codice con quei numeri e come lo sta facendo. Ho visto benchmark in cui le operazioni a 32 bit erano più veloci su determinate CPU a 32 bit rispetto allo stesso codice con operazioni a 16 bit, tuttavia ho già visto il contrario essere vero. Anche passare da un compilatore a un altro o aggiornare la versione del compilatore potrebbe già cambiare di nuovo tutto. Posso solo dire quanto segue: chiunque affermi che lavorare con i cortometraggi è significativamente più lento rispetto a lavorare con gli ints, deve fornire un codice sorgente di esempio per tale affermazione e nominare CPU e compilatore che ha usato per i test, dal momento che non ho mai provato nulla di simile all'interno circa gli ultimi 10 anni. Ci possono essere alcune situazioni in cui lavorare con gli ints è forse 1-5% più veloce, ma qualsiasi cosa al di sotto del 10% non è "significativa". e la domanda è: vale la pena sprecare il doppio della memoria in alcuni casi solo perché può offrirti prestazioni del 2%? Non credo.

Dipende. Se si è collegati alla CPU, le operazioni a 32 bit su una CPU a 32 bit saranno più veloci di 16 bit. Se sei legato alla memoria (in particolare se hai troppi errori nella cache L2), utilizza i dati più piccoli in cui puoi comprimere.

Puoi scoprire quale stai usando un profiler che misurerà i guasti di CPU e L2 come VTune di Intel . Eseguirai la tua app 2 volte con lo stesso carico e unirai le 2 esecuzioni in una vista degli hotspot della tua app e potrai vedere per ogni riga di codice quanti cicli sono stati spesi su quella linea. Se in una costosa riga di codice vengono visualizzati 0 errori cache, si è associati alla CPU. Se vedi tonnellate di miss, sei legato alla memoria.

Non ascoltare il consiglio, provalo.

Probabilmente dipenderà molto dall'hardware / compilatore che stai usando. Un test rapido dovrebbe rendere breve questa domanda. Probabilmente meno tempo per scrivere il test che per scrivere la domanda qui.

Se si sta operando su un set di dati di grandi dimensioni, la preoccupazione maggiore è l'impronta di memoria. Un buon modello in questo caso è supporre che la CPU sia infinitamente veloce e passare il tempo a preoccuparsi di quanti dati devono essere spostati nella / dalla memoria. In effetti, ora le CPU sono così veloci che a volte è più efficiente codificare (ad esempio, comprimere) i dati. In questo modo, la CPU fa (potenzialmente molto) più lavoro (decodifica / codifica), ma la larghezza di banda della memoria è sostanzialmente ridotta.

Quindi, se il tuo set di dati è grande, probabilmente stai meglio usando numeri interi a 16 bit. Se il tuo elenco è ordinato, potresti progettare uno schema di codifica che prevede una codifica differenziale o di lunghezza, che ridurrà ulteriormente la larghezza di banda della memoria.

Quando dici 32 bit, suppongo che intendi x86. L'aritmetica a 16 bit è piuttosto lenta: il prefisso della dimensione dell'operando rallenta la decodifica davvero . Quindi non rendere le variabili temporanee brevi int o int16_t.

Tuttavia, x86 può caricare in modo efficiente numeri interi a 16 e 8 bit nei registri a 32 o 64 bit. (movzx / movsx: zero e estensione del segno). Quindi sentiti libero di usare short int per array e campi struct, ma assicurati di usare int o long per le tue variabili temporanee.

  

Tuttavia, se sto sommando due numeri interi brevi, la CPU impacchetterebbe entrambi i valori in un unico passaggio in parallelo (estendendo così la larghezza di banda a 4 byte del bus)?

Questa è una sciocchezza. le istruzioni di caricamento / archiviazione interagiscono con la cache L1 e il fattore limitante è il numero di operazioni; la larghezza è irrilevante. per esempio. su core2: 1 carico e 1 negozio per ciclo, indipendentemente dalla larghezza. La cache L1 ha un percorso 128 o 256 bit per la cache L2.

Se i carichi sono il collo di bottiglia, può essere utile un carico largo che si divide con turni o maschere dopo il caricamento. Oppure utilizza SIMD per elaborare i dati in parallelo senza decomprimere dopo il caricamento in parallelo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top