Ottimizzare C++/Ottimizzazione del codice C++/Accesso alla memoria

Wikibooks, manuali e libri di testo liberi.
Indice del libro

L'accesso alla memoria principale da parte del processore fa implicitamente uso sia delle varie cache del processore, che del meccanismo di swapping su disco del gestore della memoria virtuale del sistema operativo.

Sia le cache del processore che il gestore della memoria virtuale elaborano i dati a blocchi, per cui il software è più veloce se pochi blocchi di memoria contengono il codice e i dati usati da un solo comando. Il principio che i dati e il codice elaborati da un comando debbano stare vicini in memoria è detto 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.

In 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[modifica]

Poni vicine nella stessa unità di compilazione tutte le definizioni di funzioni appartenenti allo stesso collo di bottiglia.

In tal modo, il codice macchina generato 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.

Le union[modifica]

In array o collezioni medi o grandi, usa le union.

Le union 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 nelle union non vengono tenute nei registri del processore.

I bit-field[modifica]

Se un oggetto medio o grande contiene più numeri interi con un range limitato, trasformali in bit-field.

I bit-field riducono le dimensioni dell'oggetto,

Per esempio, invece della seguente struttura:

struct {
    bool b;
    unsigned short ui1, ui2, ui3; // range: [0, 1000]
};

che occupa 8 byte, puoi definire la seguente struttura:

struct {
    unsigned b: 1;
    unsigned ui1: 10, ui2: 10, ui3: 10; // range: [0, 1000]
};

che occupa solamente (1 + 10 + 10 + 10 = 31 bit, 31 <= 32) 4 byte.

Per fare un altro esempio, invece del seguente array:

unsigned char a[5]; // range: [-20, +20]

che occupa 5 byte, puoi definire la seguente struttura:

struct {
    signed a1: 6, a2: 6, a3: 6, a4: 6, a5: 6; // range: [-20, +20]
};

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[modifica]

Se in un template di classe una funzione membro non banale non dipende 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:

template <typename T>
class C {
public:
    C(): x_(0) { }
    int f(int i) { body(); return i; }
private:
    T x_;
};

Può convenire sostituire tale codice con il seguente:

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; }

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 funzione di quel template di classe non dipende dai parametri del template, ad ogni istanziazione di tale funzione il suo codice macchina verrà duplicato. Tale replicazione di codice ingrandisce inutilmente il programma.

In un template di classe o in un template di funzione, una grossa funzione potrebbe avere una grande porzione che non dipende da nessun parametro di template. In tal caso, in primo luogo scorpora tale porzione di codice come una funzione distinta, e poi applica questa linea-guida.