クリーム色の背景、色付き表示、高コントラスト、大きな文字のほうが読みやすいですか?配色と文字を選ぶと、すべてのページに一度で反映されます。

無料レッスン

キャッシュでメモリが速くなる理由

このレッスンのあとで: キャッシュがあるとメモリが速くなる理由を、説明することができる。

55分のレッスンコマンド6件コンパイラとアーキテクチャのラボ:gcc、flex、bison、RISC-V を1台の VM で

同じ集計なのに何倍も遅い

ある通販会社では、毎晩、大きな売上表を合計する集計を動かしています。担当者が2つのループを入れかえて、行ではなく列の順に足すようにしました。

合計は1円も変わりません。ところが集計にかかる時間が、何倍にもなりました。仕事の量は同じで、変わったのは順番だけです。原因はキャッシュです。

このレッスンのゴール

メモ

キャッシュがあるとメモリが速くなる理由を、説明することができる。

キーワード

ことばいみれい
記憶階層小さく速い記憶から、大きく遅い記憶までの段の並び
キャッシュ最近使ったデータの写しを置く、小さく速いメモリ
キャッシュラインキャッシュが一度に写すバイトのまとまり64バイト
ヒット欲しいデータが、もうキャッシュにあること
ミスデータを、遅いメモリから持ってくる必要があること
局所性プログラムが、同じデータや近くのデータをまた使う性質

学生が入力するセルは、このプレビューでは空白です。

メモリは何段にも分かれている

プロセッサは、足し算を1ナノ秒よりずっと短い時間で行います。ところがメインメモリを読むには、その100倍ほどかかります。

そこでコンピュータは記憶階層を持ちます。プロセッサのすぐそばに、小さくて速いキャッシュがあります。L1、L2、そして多くの場合 L3 です。その後ろにメインメモリ、さらに後ろにディスクがあります。下の段ほど大きく、遅くなります。

例: よくあるノートパソコン

段よくある大きさ読むのにかかるおよその時間
レジスタ8バイトが32個1ナノ秒未満
L1 キャッシュ32 から 64 KB約1ナノ秒
L2 キャッシュ数百 KB から数 MB数ナノ秒
L3 キャッシュ数 MB から数十 MB10 から 20 ナノ秒
メインメモリ数 GB約100ナノ秒
SSD数百 GB数万ナノ秒

学生が入力するセルは、このプレビューでは空白です。

アーキテクチャのフォルダへ移る

コマンド
cd ~/arch

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

ここのキャッシュラインの大きさ

コマンド
getconf LEVEL1_DCACHE_LINESIZE

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

ミスのときは、ライン1本を写す

キャッシュは、1バイトだけを写すことはしません。ミスのときは、下の段からキャッシュライン1本、ふつう64バイトをまとめて写します。

これが得になるのは、局所性があるからです。プログラムは、今読んだデータのすぐ隣をまもなく読みます。これが空間的局所性です。同じデータをまた読むこともあります。これが時間的局所性です。どちらの場合も、次の読みこみはヒットになります。

loop.c で行の順が速い理由

loop.c には、4096行 × 4096列の int の表があります。int は4バイトなので、1行は16384バイトで、64バイトのライン1本には数が16個入ります。

行の順に読むと、1回のミスで次の16個が入るので、16回のうち15回はヒットです。列の順に読むと、次の数はいつも1行先で、別のラインにあります。そのラインに戻ってくるころには、もう追い出されています。

表全体の MB と、ライン1本の数

コマンド
python3 -c "print(4096 * 4096 * 4 // 1024 // 1024, 64 // 4)"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

ラボの loop プログラムを作る

コマンド
gcc -O2 -o loop loop.c

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

2つの順番の時間を計る

コマンド
./loop

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

よくある間違い

注意

「遅いほうのループは、仕事を多くしているはずだ」と考える人が多くいます。そうではありません。どちらも同じ16777216個の数を足し、同じ和を出します。違うのは、プロセッサがメモリを待つ回数です。コードが遅いときは、命令の数だけでなく、ミスの数も数えましょう。

ダイレクトマップ方式で、ラインの場所を決める

ダイレクトマップ方式のキャッシュでは、どの番地も、入れるラインがちょうど1つに決まっています。番地を3つに分けて考えます。

オフセットは、番地をラインの大きさで割った余りで、ラインの中の何バイト目かです。番地をラインの大きさで割った商がブロック番号です。インデックスはブロック番号をライン数で割った余りで、どのラインかを決めます。タグは残りの部分で、今そのラインにどのブロックがあるかを表します。

例: 32バイトのラインが4本

番地ブロックインデックスタグ結果
0000ミス: ライン0は空
8000ヒット
40110ミス: ライン1は空
128401ミス: ブロック0を追い出す
0000ミス: ブロック0は追い出された
44110ヒット

学生が入力するセルは、このプレビューでは空白です。

0x3a7c を、32バイト16本で分ける

コマンド
python3 -c "print(0x3a7c % 32, 0x3a7c // 32 % 16, 0x3a7c // 512)"

ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。

さらに挑戦

sed 's/4096/3000/' loop.c > odd.c で、1辺が3000の表を作るコピーを作ってください。gcc -O2 -o odd odd.c で作り、./odd を何回か実行します。

和は、実行する前に予想しましょう。次に、2つの順番の差を ./loop の差と比べます。1行のバイト数が2の累乗でなくなると何が変わるのか、シミュレータでインデックスを調べて説明してください。

このラボとソースについて: コンパイラとアーキテクチャのラボ:gcc、flex、bison、RISC-V を1台の VM で

授業で使いますか?このモジュールを学生に割り当てると、教員用ガイド、自動採点つきの課題、学生ごとのラボを使えます。