Wie andere bereits gesagt haben, liegt das Problem in der Speicherung an der Speicherstelle im Array: x[i][j]
. Hier ist ein kleiner Einblick, warum:
Sie haben ein 2-dimensionales Feld, aber der Speicher im Computer ist von Natur aus 1-dimensional. Stellen Sie sich Ihr Array also wie folgt vor:
0,0 | 0,1 | 0,2 | 0,3
----+-----+-----+----
1,0 | 1,1 | 1,2 | 1,3
----+-----+-----+----
2,0 | 2,1 | 2,2 | 2,3
Ihr Computer speichert sie als eine einzige Zeile im Speicher:
0,0 | 0,1 | 0,2 | 0,3 | 1,0 | 1,1 | 1,2 | 1,3 | 2,0 | 2,1 | 2,2 | 2,3
Im 2. Beispiel greifen Sie auf das Array zu, indem Sie zuerst eine Schleife über die zweite Zahl ziehen, d.h.:
x[0][0]
x[0][1]
x[0][2]
x[0][3]
x[1][0] etc...
Das bedeutet, dass du sie alle der Reihe nach triffst. Schauen Sie sich nun die 1. Version an. Sie tun:
x[0][0]
x[1][0]
x[2][0]
x[0][1]
x[1][1] etc...
Aufgrund der Art und Weise, wie C das 2-D-Array im Speicher angeordnet hat, bitten Sie es, überall hin zu springen. Aber jetzt kommt der Knackpunkt: Warum ist das wichtig? Alle Speicherzugriffe sind doch gleich, oder?
Nein: wegen der Caches. Daten aus dem Arbeitsspeicher werden in kleinen Stücken (so genannten "Cache-Zeilen") zur CPU übertragen, in der Regel 64 Byte. Wenn Sie 4-Byte-Ganzzahlen haben, bedeutet das, dass Sie 16 aufeinanderfolgende Ganzzahlen in einem hübschen kleinen Bündel erhalten. Es ist eigentlich ziemlich langsam, diese Speicherstücke zu holen; Ihre CPU kann in der Zeit, die eine einzelne Cache-Zeile zum Laden braucht, eine Menge Arbeit erledigen.
Schauen Sie sich nun die Reihenfolge der Zugriffe an: Im zweiten Beispiel wird (1) ein Stück von 16 Ints gegriffen, (2) alle geändert, (3) 4000*4000/16 Mal wiederholt. Das ist schön und schnell, und die CPU hat immer etwas zu tun.
Das erste Beispiel besteht darin, (1) ein Stück von 16 Ints zu nehmen, (2) nur eines davon zu ändern, (3) 4000*4000 Mal zu wiederholen. Das erfordert die 16-fache Anzahl von "Abrufen" aus dem Speicher. Ihre CPU muss also Zeit damit verbringen, darauf zu warten, dass der Speicher auftaucht, und währenddessen verschwenden Sie wertvolle Zeit.
Wichtiger Hinweis:
Nun, da Sie die Antwort kennen, noch ein interessanter Hinweis: Es gibt keinen zwingenden Grund dafür, dass Ihr zweites Beispiel das schnelle sein muss. In Fortran zum Beispiel wäre das erste Beispiel schnell und das zweite langsam. Das liegt daran, dass Fortran die Dinge nicht wie C in konzeptionelle "Zeilen" expandiert, sondern in "Spalten", d.h.:
0,0 | 1,0 | 2,0 | 0,1 | 1,1 | 2,1 | 0,2 | 1,2 | 2,2 | 0,3 | 1,3 | 2,3
Das Layout von C wird als "Zeilen-Dur" und das von Fortran als "Spalten-Dur" bezeichnet. Wie Sie sehen, ist es sehr wichtig zu wissen, ob Ihre Programmiersprache zeilenmajor oder spaltenmajor ist! Hier ist ein Link für weitere Informationen: http://en.wikipedia.org/wiki/Row-major_order