Como otros han dicho, la cuestión es la tienda a la posición de memoria en la matriz: x[i][j]
. Aquí hay una idea de por qué:
Tiene una matriz bidimensional, pero la memoria en la computadora es inherentemente unidimensional. Entonces, mientras imaginas tu matriz así:
0,0 | 0,1 | 0,2 | 0,3
----+-----+-----+----
1,0 | 1,1 | 1,2 | 1,3
----+-----+-----+----
2,0 | 2,1 | 2,2 | 2,3
Su computadora lo almacena en la memoria como una sola línea:
0,0 | 0,1 | 0,2 | 0,3 | 1,0 | 1,1 | 1,2 | 1,3 | 2,0 | 2,1 | 2,2 | 2,3
En el segundo ejemplo, accede a la matriz al recorrer primero el segundo número, es decir:
x[0][0]
x[0][1]
x[0][2]
x[0][3]
x[1][0] etc...
Lo que significa que los estás golpeando a todos en orden. Ahora mira la primera versión. Estás haciendo:
x[0][0]
x[1][0]
x[2][0]
x[0][1]
x[1][1] etc...
Debido a la forma en que C presentó la matriz de 2-d en la memoria, le está pidiendo que salte por todo el lugar. Pero ahora para el pateador: ¿Por qué importa esto? Todos los accesos a la memoria son iguales, ¿verdad?
No: por cachés. Los datos de su memoria se transfieren a la CPU en pequeños fragmentos (llamados 'líneas de caché'), generalmente 64 bytes. Si tiene enteros de 4 bytes, eso significa que está obteniendo 16 enteros consecutivos en un pequeño paquete ordenado. En realidad, es bastante lento obtener estos trozos de memoria; su CPU puede hacer mucho trabajo en el tiempo que tarda en cargar una sola línea de caché.
Ahora mire hacia atrás en el orden de los accesos: el segundo ejemplo es (1) tomar un trozo de 16 ints, (2) modificarlos todos, (3) repetir 4000 * 4000/16 veces. Eso es bueno y rápido, y la CPU siempre tiene algo en qué trabajar.
El primer ejemplo es (1) tomar un trozo de 16 pulgadas, (2) modificar solo uno de ellos, (3) repetir 4000 * 4000 veces. Eso requerirá 16 veces el número de "recuperaciones" de la memoria. Su CPU realmente tendrá que pasar tiempo sentado esperando a que aparezca esa memoria, y mientras está sentado, está perdiendo un tiempo valioso.
Nota IMPORTANTE:
Ahora que tiene la respuesta, aquí hay una nota interesante: no hay una razón inherente para que su segundo ejemplo tenga que ser rápido. Por ejemplo, en Fortran, el primer ejemplo sería rápido y el segundo lento. Eso es porque en lugar de expandir las cosas en "filas" conceptuales como lo hace C, Fortran se expande en "columnas", es decir:
0,0 | 1,0 | 2,0 | 0,1 | 1,1 | 2,1 | 0,2 | 1,2 | 2,2 | 0,3 | 1,3 | 2,3
El diseño de C se llama 'row-major' y Fortran's se llama 'column-major'. Como puede ver, ¡es muy importante saber si su lenguaje de programación es mayor de fila o mayor de columna! Aquí hay un enlace para obtener más información: http://en.wikipedia.org/wiki/Row-major_order