Como já foi dito, a questão é a loja para o local de memória na matriz: x[i][j]
. Aqui está um pouco do porquê:
Você tem uma matriz bidimensional, mas a memória do computador é inerentemente unidimensional. Então, enquanto você imagina sua matriz assim:
0,0 | 0,1 | 0,2 | 0,3
----+-----+-----+----
1,0 | 1,1 | 1,2 | 1,3
----+-----+-----+----
2,0 | 2,1 | 2,2 | 2,3
O seu computador armazena-o na memória como uma única linha:
0,0 | 0,1 | 0,2 | 0,3 | 1,0 | 1,1 | 1,2 | 1,3 | 2,0 | 2,1 | 2,2 | 2,3
No segundo exemplo, você acessa a matriz fazendo um loop sobre o segundo número primeiro, ou seja:
x[0][0]
x[0][1]
x[0][2]
x[0][3]
x[1][0] etc...
Significando que você está acertando todos eles em ordem. Agora olhe para a 1ª versão. Voce esta fazendo:
x[0][0]
x[1][0]
x[2][0]
x[0][1]
x[1][1] etc...
Devido à maneira como C organizou a matriz 2-d na memória, você está pedindo que ela salte por todo o lado. Mas agora para o kicker: Por que isso importa? Todos os acessos à memória são iguais, certo?
Não: por causa dos caches. Os dados da sua memória são trazidos para a CPU em pequenos pedaços (chamados de 'linhas de cache'), normalmente de 64 bytes. Se você tem números inteiros de 4 bytes, significa que você está obtendo 16 números inteiros consecutivos em um pequeno pacote. Na verdade, é bastante lento buscar esses pedaços de memória; sua CPU pode fazer muito trabalho no tempo necessário para carregar uma única linha de cache.
Agora, olhe novamente para a ordem dos acessos: O segundo exemplo é (1) pegar um pedaço de 16 polegadas, (2) modificar todos eles, (3) repetir 4000 * 4000/16 vezes. Isso é agradável e rápido, e a CPU sempre tem algo para trabalhar.
O primeiro exemplo é (1) pegue um pedaço de 16 polegadas, (2) modifique apenas um deles, (3) repita 4000 * 4000 vezes. Isso exigirá 16 vezes o número de "buscas" da memória. Na verdade, sua CPU terá que gastar um tempo esperando que a memória apareça e, enquanto estiver sentado, você estará perdendo um tempo valioso.
Nota importante:
Agora que você tem a resposta, eis uma observação interessante: não há razão inerente para que seu segundo exemplo seja o mais rápido. Por exemplo, no Fortran, o primeiro exemplo seria rápido e o segundo lento. Isso ocorre porque, em vez de expandir as coisas em "linhas" conceituais, como C faz, o Fortran se expande em "colunas", ou seja:
0,0 | 1,0 | 2,0 | 0,1 | 1,1 | 2,1 | 0,2 | 1,2 | 2,2 | 0,3 | 1,3 | 2,3
O layout de C é chamado de 'linha principal' e o de Fortran é chamado de 'coluna principal'. Como você pode ver, é muito importante saber se a sua linguagem de programação é de linhas principais ou de colunas! Aqui está um link para mais informações: http://en.wikipedia.org/wiki/Row-major_order