他の人が言ったように、問題は配列内のメモリ位置へのストアです: x[i][j]
. その理由を少し説明します。
2 次元配列がありますが、コンピューターのメモリは本質的に 1 次元です。したがって、配列を次のように想像している間:
0,0 | 0,1 | 0,2 | 0,3
----+-----+-----+----
1,0 | 1,1 | 1,2 | 1,3
----+-----+-----+----
2,0 | 2,1 | 2,2 | 2,3
あなたのコンピュータはそれを単一の行としてメモリに保存します:
0,0 | 0,1 | 0,2 | 0,3 | 1,0 | 1,1 | 1,2 | 1,3 | 2,0 | 2,1 | 2,2 | 2,3
2 番目の例では、最初に 2 番目の数値をループして配列にアクセスします。
x[0][0]
x[0][1]
x[0][2]
x[0][3]
x[1][0] etc...
順番に叩いているということです。それでは第1版をご覧ください。あなたはやっている:
x[0][0]
x[1][0]
x[2][0]
x[0][1]
x[1][1] etc...
C が 2 次元配列をメモリに配置する方法のために、あちこちにジャンプするように要求しています。しかし、キッカーのために: なぜこれが重要なのですか? すべてのメモリアクセスは同じですよね?
いいえ: キャッシュのためです。メモリからのデータは、通常 64 バイトの小さなチャンク (「キャッシュ ライン」と呼ばれる) で CPU に渡されます。4 バイトの整数がある場合、それは 16 個の連続した整数をきちんとした小さなバンドルで取得していることを意味します。これらのメモリのチャンクをフェッチするのは実際にはかなり遅いです。CPU は、単一のキャッシュ ラインをロードするのにかかる時間内に多くの作業を行うことができます。
アクセスの順序を振り返ってみましょう。2 番目の例は、(1) 16 個の int のチャンクを取得し、(2) それらすべてを変更し、(3) 4000*4000/16 回繰り返します。これは素晴らしく高速であり、CPU は常に何か作業を行っています。
最初の例は、(1) 16 個の int のチャンクを取得し、(2) そのうちの 1 つだけを変更し、(3) 4000*4000 回繰り返します。これには、メモリからの「フェッチ」回数の 16 倍が必要になります。CPU は実際には、そのメモリが表示されるのを待つために時間を費やす必要があります。CPU が座っている間、貴重な時間を無駄にしています。
重要な注意点:
答えが得られたので、ここに興味深いメモがあります。2 番目の例が高速でなければならない固有の理由はありません。たとえば、Fortran では、最初の例は高速で、2 番目の例は低速です。これは、C のように物事を概念的な「行」に展開する代わりに、Fortran は「列」に展開するためです。
0,0 | 1,0 | 2,0 | 0,1 | 1,1 | 2,1 | 0,2 | 1,2 | 2,2 | 0,3 | 1,3 | 2,3
C のレイアウトは「行優先」と呼ばれ、Fortran のレイアウトは「列優先」と呼ばれます。ご覧のとおり、プログラミング言語が行優先か列優先かを知ることは非常に重要です。詳細については、次のリンクを参照してください: http://en.wikipedia.org/wiki/Row-major_order