Ottimizzare C++/Scrivere codice C++ efficiente/Accesso alla memoria

Indice del libro

Questa sezione presenta le linee-guida per migliorare le prestazioni di accesso alla memoria principale, facendo buon uso delle memorie cache del processore, e dello swapping su hard disk del gestore di memoria virtuale del sistema operativo.

Ordine di accesso alla memoria

modifica

Accedi alla memoria in ordine crescente. In particolare:

  • scandisci gli array in ordine crescente;
  • scandisci gli array multidimensionali usando gli indice più a destra per i cicli più interni;
  • nei costruttori delle classi e negli operatori di assegnamento (operator=) accedi alle variabili membro nell'ordine in cui sono dichiarate nella classe.

La cache dei dati ottimizza gli accessi alla memoria in ordine sequenziale crescente.

Quando si itera su un array multidimensionale, il ciclo più interno dovrebbe iterare sull'ultimo indice, il ciclo appena più esterno dovrebbe iterare sul penultimo indice, e così via. In tal modo, è garantito che le celle vengono elaborate nello stesso ordine in cui si trovano in memoria. Per esempio, il seguente codice è ottimizzato:

float a[num_livelli][num_righe][num_colonne];
for (int liv = 0; liv < num_livelli; ++liv) {
    for (int r = 0; r < num_righe; ++r) {
         for (int c = 0; c < num_colonne; ++c) {
            a[liv][r][c] += 1;
        }
    }
}

Allineamento di memoria

modifica

Lascia l'allineamento di memoria suggerito dal compilatore.

I compilatori attivano di default un criterio di allineamento dei tipi fondamentali, per cui gli oggetti possono avere solamente indirizzi di memoria che sono un multiplo di fattori particolari. Tale criterio garantisce le massime prestazioni, ma può introdurre degli spazi inutilizzati (in inglese, padding) tra oggetti consecutivi.

Se per alcune strutture è necessario eliminare tali spazi, usa le direttiva pragma solamente intorno alle definizioni di tali strutture.

Raggruppamento di funzioni in unità di compilazione

modifica

Definisci nella stessa unità di compilazione tutte le funzioni membro di una classe, tutte le funzioni friend di tale classe, e tutte le funzioni delle classi friend di tale classe, a meno che il file risultante diventi scomodo da gestire per la sua dimensione eccessiva.

In tal modo, sia il codice macchina prodotto compilando tali funzioni sia i dati statici definiti in tali classi e funzioni avranno indirizzi vicini tra loro; inoltre, così si consente anche ai compilatori che non effettuano ottimizzazioni sull'intero programma di ottimizzare le chiamate tra tali funzioni.

Raggruppamento di variabili in unità di compilazione

modifica

Definisci ogni variabile globale nell'unità di compilazione in cui è usata più spesso.

In tal modo, tali variabili avranno indirizzi vicini tra loro e vicini a quelli delle variabili statiche definite in tale unità di compilazione; inoltre, così si consente anche ai compilatori che non effettuano ottimizzazioni sull'intero programma di ottimizzare l'accesso a tali variabili da parte delle funzioni che le usano maggiormente.

Funzioni e variabili private in unità di compilazione

modifica

Dichiara in un namespace anonimo le variabili e le funzioni globali a un'unità di compilazione, ma non usate da altre unità di compilazione.

In linguaggio C e anche in C++, tali variabili e funzioni possono essere dichiarate static.

Tuttavia, nel C++ moderno, l'uso di variabili e funzioni globali static è deprecato, e dovrebbe essere sostituito da variabili e funzioni dichiarate in un namespace anonimo.

In entrambi i casi, si dichiara al compilatore che tali identificatori non verranno usati da altre unità di compilazione. Questo permette anche ai compilatori che non effettuano ottimizzazioni sull'intero programma di ottimizzare l'utilizzo di tali variabili e funzioni.