Come @dlev menzionato, questo è dovuto a locality of reference e ha a che fare con il funzionamento dell'hardware fisico nel computer.
All'interno del computer, ci sono molti diversi tipi di memoria. In genere, solo determinate posizioni di memoria (registri) possono avere effettive operazioni eseguite su di esse; il resto del tempo, se stai eseguendo operazioni sui dati, devi caricarlo dalla memoria in un registro, eseguire alcuni calcoli, quindi riscriverli.
La memoria principale (RAM) è molto, molto più lenta dei registri, spesso di un fattore da centinaia a migliaia. Di conseguenza, la lettura dalla memoria dovrebbe essere evitata se possibile. Per risolvere questo problema, la maggior parte dei computer dispone di aree di memoria speciali chiamate caches. Il lavoro della cache è di conservare i dati a cui è stato recentemente effettuato l'accesso dalla memoria in modo tale che, se si accede nuovamente alla stessa area di memoria, il valore può essere estratto dalla cache (veloce) anziché dalla memoria principale (lento). In genere, le cache sono progettate in modo che se un valore viene letto dalla memoria, quel valore, più un intero gruppo di valori adiacenti, viene inserito nella cache. In questo modo, se si esegue un'iterazione su un array, dopo aver letto il primo valore, il resto dei valori dell'array si troverà nella cache e sarà possibile accedervi in modo più efficiente.
Il motivo per cui il codice è più lento di quello che deve essere è che non accede agli elementi dell'array in modo sequenziale. In C, gli array 2D sono disposti in row-major order, il che significa che la memoria è organizzato come
A[0][0] A[0][4] A[0][5] ... A[1][0] A[1][6] A[1][7] ... A[2][0] A[2][8] A[2][9] ...
Di conseguenza, se si utilizza questo ciclo for:
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
// Do something with A[i][j]
}
}
Quindi si ottiene eccellente località, perché si accedere agli elementi dell'array nell'ordine in cui appaiono in memoria. Questo rende il numero di letture della memoria principale molto piccolo, dato che tutto è tipicamente in cache e pronto a partire.
Tuttavia, se scambiate i loop, come avete fatto, i vostri accessi saltano in memoria e non sono necessariamente consecutivi. Ciò significa che avrete un sacco di errori di memoria nella cache in cui l'indirizzo di memoria che leggete dopo non è nella cache. Ciò aumenta il numero di carichi della cache, che possono rallentare notevolmente il programma.
I compilatori stanno iniziando a diventare abbastanza intelligenti da scambiare automaticamente loop di questo tipo, ma siamo ancora lontani dall'essere in grado di ignorare questi dettagli. Come regola generale, quando si scrive codice C o C++ per matrici multidimensionali, provare ad eseguire l'iterazione in ordine di riga principale anziché in ordine di colonna principale. È possibile ottenere aumenti notevoli nel programma.
Spero che questo aiuti!
Località di riferimento: si sta invalidando inutilmente la cache della CPU in modo "lento". – dlev
@dlev: perché non dovresti pubblicare questo come risposta? –
perché dlev non riguarda la ripetizione. dlev parla dell'amore – Robotnik