他の人が言ったように、問題は配列のメモリ位置へのストア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...
あなたが順番にそれらすべてを打っていることを意味します。では、最初のバージョンを見てください。あなたはやっている:
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整数のチャンクを取得する、(2)すべてを変更する、(3)4000 * 4000/16回繰り返す。それは素晴らしくて高速であり、CPUには常に何らかの処理が必要です。
最初の例は、(1)16整数のチャンクを取得する、(2)そのうちの1つだけを変更する、(3)4000 * 4000回繰り返す。これは、メモリからの「フェッチ」の数の16倍を必要とします。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