Ottimizzare C++/Ottimizzazione del codice C++/Accesso alla memoria: differenze tra le versioni

Contenuto cancellato Contenuto aggiunto
RamaccoloBot (discussione | contributi)
Riportate modifiche da en.wikibooks
Riga 1:
{{Ottimizzare C++}}
 
L'accesso alla memoria principale da parte del processore fa implicitamente uso sia delle varie cache del processore, che dellodel meccanismo di swapping su disco del gestore della memoria virtuale del sistema operativo.
Sia le cache del processore che la memoria virtuale elaborano i dati a blocchi, per cui si velocizza il programma se si riesce a porre nello stesso blocco il codice o i dati utilizzati dalla stesso comando.
Il principio che i dati e il codice elaborati da un comando da un comando debbano stare vicini in memoria è detto [[w:en:Locality of reference|''località dei riferimenti'']].
 
Sia le cache del processore che lail gestore della memoria virtuale elaborano i dati a blocchi, per cui si velocizza il programmasoftware seè sipiù riesceveloce ase porrepochi nelloblocchi stessodi bloccomemoria contengono il codice oe i dati utilizzatiusati da dallaun stessosolo comando.
Qui vengono proposte delle tecniche che ottimizzano l'uso delle cache e della memoria virtuale, tramite un aumento della località dei riferimenti.
Il principio che i dati e il codice elaborati da un comando da un comando debbano stare vicini in memoria è detto [[w:en:Locality of reference|''località dei riferimenti'']].
 
Questo principio diventa ancora più importante per le prestazioni di applicazioni multi-threaded su sistemi multi-core, dato che se più thread in esecuzione in core distinti accedono allo stesso blocco di cache, la contesa provoca un degrado delle prestazioni.
 
QuiIn questa sezione vengono proposte delle tecniche che ottimizzano l'uso delle cache del processore e della memoria virtuale, tramite un aumento della località dei riferimenti del codice e dei dati.
 
=== Avvicinare il codice ===
 
'''DichiaraPoni vicine nella stessa unità di compilazione tutte le definizioni di funzioni appartenenti aallo unstesso collo di bottiglia.'''
 
In tal modo, il codice macchina prodottogenerato compilando tali funzioni avrà indirizzi vicini, e quindi maggiore località dei riferimenti del codice.
 
Un'altra conseguenza positiva è che i dati statici locali dichiarati e usati da tali funzioni avranno indirizzi vicini, e quindi maggiore località dei riferimenti dei dati.
Analogo effetto si ha sulla località dei riferimenti dei dati per i dati statici dichiarati in tale codice.
 
=== Le <code>union</code> ===
 
'''In array o collezioni medi o grandi, usa le <code>union</code>.'''
 
Le <code>union</code> permettono di risparmiare memoria in strutture di tipi variabili, e quindi di renderle più compatte.
 
Però non usarle in oggetti piccoli o piccolissimi, in quanto non si hanno vantaggi significativi per il risparmio di memoria, e con alcuni compilatori le variabili poste nellanelle <code>union</code> non vengono tenute nei registri del processore.
 
=== I ''bit-field'' ===
 
'''Se un oggetto medio o grande contiene deipiù numeri interi con un range limitato, trasformali in ''bit-field''.'''
 
I ''bit-field'' riducono le dimensioni dell'oggetto, e quindi permettono di far stare più oggetti nella cache dei dati e nella memoria fisica, riducendo, rispettivamente, il ricorso alla memoria principale e al disco fisso.
 
Per esempio, invece della seguente struttura:
Per esempio, per memorizzare un bit e tre numeri compresi tra 0 e 1000, puoi usare un campo <code>unsigned</code> di un bit e tre campi <code>unsigned</code> di 10 bit ciascuno (1 + 10 + 10 + 10 = 31, 31 <= 32), mentre per memorizzare cinque numeri che possono essere compresi tra -20 e +20, puoi usare cinque campi ''signed'' di 6 bit ciascuno.
 
In entrambi i casi, tutti i bit-field stanno in un registro da 32 bit.
<source lang=cpp>
struct {
bool b;
unsigned short ui1, ui2, ui3; // range: [0, 1000]
};
</source>
 
che occupa 8 byte, puoi definire la seguente struttura:
 
<source lang=cpp>
struct {
unsigned b: 1;
unsigned ui1: 10, ui2: 10, ui3: 10; // range: [0, 1000]
};
</source>
 
che occupa solamente (1 + 10 + 10 + 10 = 31 bit, 31 <= 32) 4 byte.
 
Per fare un altro esempio, invece del seguente array:
 
<source lang=cpp>
unsigned char a[5]; // range: [-20, +20]
</source>
 
che occupa 5 byte, puoi definire la seguente struttura:
 
<source lang=cpp>
struct {
signed a1: 6, a2: 6, a3: 6, a4: 6, a5: 6; // range: [-20, +20]
};
</source>
 
che occupa solamente (6 + 6 + 6 + 6 + 6 = 30 bits, 30 <= 32) 4 bytes.
 
Tuttavia, c'è una penalità prestazionale nell'impaccare e disimpaccare i campi.
Inoltre, nell'ultimo esempio, i campi non sono più accessibile tramite un indice.
 
=== Codice di template non dipendente dai parametri ===
 
'''NeiSe in un template di classe, evitauna funzionifunzione membro non banali chebanale non dipendanodipende da nessun parametro del template, definisci una funzione non-membro avente lo stesso corpo, e sostituisci il corpo della funzione originale con una chiamata alla nuova funzione.'''
 
Supponiamo di aver scritto il seguente codice:
Tutto il codice di una funzione di un template viene espanso ad ogni istanziazione distinta di tale funzione.
Se una porzione di codice non dipende dai parametri del template, ad ogni istanziazione del template verrà generato sempre lo stesso codice.
Tale replicazione di codice ingrandisce inutilmente il programma.
 
<source lang=cpp>
Per evitare questa inefficienza, definisci tali funzioni come non appartenenti al template, e richiamale dalle funzioni membro del template.
template <typename T>
class C {
public:
C(): x_(0) { }
int f(int i) { body(); return i; }
private:
T x_;
};
</source>
 
Può convenire sostituire tale codice con il seguente:
 
<source lang=cpp>
template <typename T>
class C {
public:
C(): x_(0) { }
void f(int i) { return f_(i); }
private:
T x_;
};
 
void f_(int i) { body(); return i; }
</source>
 
Ad ogni istanziazione di un template di classe che fa uso di una funzione di quel template di classe, tutto il codice di quella funzione viene istanziato.
Se una porzionefunzione di codicequel template di classe non dipende dai parametri del template, ad ogni istanziazione deldi templatetale verràfunzione generatoil sempresuo locodice stessomacchina codiceverrà duplicato.
Tale replicazione di codice ingrandisce inutilmente il programma.
 
UnaIn funzioneun template di grandeclasse dimensioneo in un template di funzione, una grossa funzione potrebbe avere una grande porzione che non dipende da nessun parametro del template di classe o del template di funzione.
In tal caso, in primo luogo scorpora tale porzione di codice come una funzione distinta, e poi applica questa linea-guida.