Como @dlev mencionado, esto se debe a locality of reference y tiene que ver con cómo funciona el hardware físico en la computadora.
Dentro de la computadora, hay muchos tipos diferentes de memoria. Típicamente, solo ciertas ubicaciones de memoria (registros) pueden tener operaciones reales realizadas en ellos; el resto del tiempo, si está realizando operaciones en datos, debe cargarlo desde la memoria en un registro, realizar algunos cálculos y luego volver a escribirlos.
La memoria principal (RAM) es mucho, mucho más lenta que los registros, a menudo por un factor de cientos a miles. En consecuencia, la lectura de la memoria debe evitarse si es posible. Para solucionar esto, la mayoría de las computadoras generalmente tienen regiones de memoria especiales llamadas caches. El trabajo de la memoria caché es contener datos a los que se ha accedido recientemente desde la memoria, de modo que si se accede nuevamente a esa misma región de memoria, el valor se puede extraer de la memoria caché (rápido) en lugar de desde la memoria principal (lenta). Normalmente, las memorias caché están diseñadas de modo que si se lee un valor de la memoria, ese valor, más un conjunto completo de valores adyacentes, se extraen en la memoria caché. De esta forma, si itera sobre una matriz, luego de leer el primer valor, el resto de los valores de la matriz se ubicará en la memoria caché y se podrá acceder a ella de manera más eficiente.
El motivo por el que el código es más lento de lo que debe ser es que no accede a los elementos de la matriz de forma secuencial. En C, las matrices 2D se establecen en row-major order, lo que significa que la memoria está dispuesto como
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] ...
En consecuencia, si se utiliza este bucle for:
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
// Do something with A[i][j]
}
}
, entonces obtendrá una excelente localidad, ya que se acceder a los elementos de la matriz en el orden en que aparecen en la memoria. Esto hace que el número de lecturas de la memoria principal sea muy pequeño, ya que todo está típicamente en caché y listo para funcionar.
Sin embargo, si intercambia los bucles, como lo ha hecho, sus accesos saltan en la memoria y no son necesariamente consecutivos. Esto significa que tendrá un montón de falta de memoria caché en la que la dirección de memoria que lee a continuación no está en la memoria caché. Esto aumenta la cantidad de cargas de caché, lo que puede ralentizar drásticamente el programa.
Los compiladores están empezando a ser lo suficientemente inteligentes como para intercambiar los bucles de este modo de forma automática, pero todavía estamos lejos de poder ignorar estos detalles. Como regla general, al escribir código C o C++ para matrices multidimensionales, intente iterar en orden mayor de fila en lugar de orden de columna mayor. Puedes obtener aceleraciones notables en tu programa.
Espero que esto ayude!
Localidad de referencia: invalida innecesariamente la memoria caché de la CPU de forma "lenta". – dlev
@dlev: ¿por qué no publicaría esto como respuesta? –
porque dlev no se trata de la reputación. dlev es sobre el amor – Robotnik