[解決済み] 2次元配列の反復処理において、ループの順序がパフォーマンスに影響するのはなぜですか?
質問
以下の2つのプログラムは、私が "Space "と "Space "を入れ替えた以外はほとんど同じです。
i
と
j
という変数があります。どちらも異なる時間で実行されます。なぜこのようなことが起こるのか、どなたか説明していただけませんか?
バージョン1
#include <stdio.h>
#include <stdlib.h>
main () {
int i,j;
static int x[4000][4000];
for (i = 0; i < 4000; i++) {
for (j = 0; j < 4000; j++) {
x[j][i] = i + j; }
}
}
バージョン2
#include <stdio.h>
#include <stdlib.h>
main () {
int i,j;
static int x[4000][4000];
for (j = 0; j < 4000; j++) {
for (i = 0; i < 4000; i++) {
x[j][i] = i + j; }
}
}
解決方法は?
他の方もおっしゃっているように、配列のメモリ位置へのストアが問題なのです。
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
コンピュータはそれを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
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は、1つのキャッシュラインがロードされる間に、多くの作業を行うことができます。
では、アクセスの順番を振り返ってみましょう。2番目の例は、(1)16個のintの塊を取得し、(2)それらすべてを変更し、(3)4000*4000/16回繰り返しています。これなら高速で、CPUは常に何か作業をすることができます。
最初の例は、(1)16個のintの塊をつかみ、(2)そのうちの1つだけを変更し、(3)4000*4000回繰り返すというものです。これには、メモリから16倍の"fetch"が必要になります。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
関連
-
Cエラー [エラー] 代入_Ashesの左オペランドにlvalueが必要です-プログラマーズ・シークレット
-
[解決済み] "static const" vs "#define" vs "enum"
-
[解決済み] 0.1fを0にすると、なぜ10倍もパフォーマンスが落ちるのですか?
-
[解決済み] Cプリプロセッサはなぜ "linux "という単語を定数 "1 "と解釈するのですか?
-
[解決済み] Collatz予想の検証を行うC++のコードは、なぜ手書きのアセンブリよりも高速に動作するのでしょうか?
-
[解決済み] 8192個の要素にループをかけると、プログラムが遅くなるのはなぜですか?
-
[解決済み] なぜpythonはforやwhileループの後に'else'を使うのですか?
-
[解決済み] FortranはC言語よりも重い計算を最適化しやすいですか?
-
[解決済み] Intel CPU の _mm_popcnt_u64 で、32 ビットのループカウンターを 64 ビットに置き換えると、パフォーマンスが著しく低下します。
-
[解決済み】Javaで(a != 0 && b != 0)よりも(a*b != 0)の方が速いのはなぜか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
_CRT_SECURE_NO_WARNINGS エラーメッセージ、解決方法
-
error: '.' トークンの前にunqualified-idを指定する必要があります。
-
VSCodeでCプログラムを書くとエラーになる:ソースファイル "stdio.h" を開くことができない
-
C: 1を求める! + 2! + 3! + ... + n! (ループ)
-
[解決済み] C言語で関数型プログラミングを行うためのツールにはどのようなものがありますか?
-
[解決済み] プログラム終了前にmallocの後にfreeをしないと本当に何が起こるのか?
-
[解決済み] printfにおけるdoubleの正しい書式指定子
-
[解決済み] char s[]とchar *sの違いは何ですか?
-
[解決済み] C言語でファイルサイズを取得するには?[重複]する
-
[解決済み] C言語の構造体(CGRectやCGPointなど)をNSLog化することは可能ですか?