cpu cache arch
DESCRIPTION
TRANSCRIPT
あなたのお家に届くまでCPUキャッシュアーキテクチャ入門(その1) !
Dora. Panda October 18th, 2014
今日のお題
CPUがデータを読み込むまでの長い長いお話です。
ソフトウェアからは見ることのできないCPU内部動作について説明します。
ハードウェア構成左図の構成で説明を進めます
CPUによって実装が異なるので、機種固有の説明は省きます。
私なりに考えた「キャッシュの基礎を理解する」ための仮想CPUです。
「そこそこ」ワーストケースで考えます。
TLBヒット、L1、L2キャッシュはミスヒット。
細かい用語は後ほど。
ChipProcessor
CPU Core
L2 Cache
Mem Ctrl
DRAM
PCIe
HW…
MMU
L1 Cache
メモリからロードしてみよう
例えば、以下のC言語プログラム上は左の赤枠のような命令に置き換えられます。
!
※ARM命令の場合
int hoge = *__bss_start__;
–The Little Prince
“Le plus important est invisible” “大切な物は目に見えないんだよ”
メモリからデータを読み出し、CPUレジスタにロードされるまで 長い旅路の物語
と、その前に…レジスタって?
パソコン初心者向け説明
CPUはメモリに情報を置く
メモリに入りきらないときはHDDに退避
CPU
DRAM HDD
レジスタ=CPU内部のメモリ領域CPUはレジスタのデータに対してのみ演算可能。
メモリの値を直接制御できない。
データ読み出しはメモリからレジスタに転送し、レジスタを読む。
データ書き込みはレジスタに書き、レジスタの内容をメモリに転送する。
CPU
DRAM HDD
演算回路
レジスタレジスタ
キャッシュ
MEMORY MANAGEMENT UNIT
About MMU ChipProcessor
CPU Core
L2 Cache
Mem Ctrl
DRAM
PCIe
HW…
MMU
L1 Cache
MMU
CPUがデータをロードするとき、MMUに対してアドレスを出力します。
MMUでは仮想メモリと物理メモリ変換を行います。
TLBというアドレス変換情報キャッシュがあります。
CPU Core
MMU仮想アドレス 物理アドレス仮想アドレス 物理アドレス仮想アドレス 物理アドレス
仮想アドレス 物理アドレス
仮想アドレス
物理アドレス
物理アドレスと仮想アドレス物理アドレス
連続的な実際のアドレス。
仮想アドレス
OSが実行時に動的に割り当てるアドレス
ページ単位の割り当て
4kB、16kB、64kBなど。
不連続なページを連続的に見せる。
プロセス毎に独立したアドレス
あるプロセスからほかのプロセスのアドレスにはアクセス不可。(メモリ保護)
物理アドレス 仮想アドレス
MMU
仮想アドレス(32bit)00000000000 00000000000 000000000000
仮想~物理アドレス変換
Page Table物理アドレス
物理アドレス(32bit)00000000000000000000 000000000000
Page TablePage Table
Page TablePage Table
物理アドレス物理アドレス物理アドレス
Page Table Entry(PTE)を使ってアドレス変換
TLBはPTEのキャッシュ
TLBミスヒット時はPTEを検索
エントリ数は32~128個程度
PTEミスヒット時はページイン(Disk
-> RAM)
LEVEL 1 CACHE
About Level 1 Cache Processor
CPU Core
L2 Cache
Mem Ctrl
DRAM
PCIe
HW…
MMU
L1 Cache
LEVEL 1 CACHE
CPUが最初にアクセスするメモリ領域
一番高速&一番サイズが小さいメモリ
CPUクロックと同一クロックで読み書き可能
数k~数100kBytes程度
MMU
物理アドレスLevel 1 Cache
データ物理アドレスデータ物理アドレスデータ物理アドレス
データ物理アドレス
Miss Hit Hit
DETAILS OF CACHE
Level 1 Cache
アドレス00000000000000000 000000000 000000
データ(1 word)00000000 00000000 00000000 00000000
Cache Entry Table
タグ(上位アドレス) データタグ(上位アドレス) データタグ(上位アドレス) データ
タグ(上位アドレス) データ
=?
データ1
1 word (4bytes)
データ2
データ3
データn①
②③
01
511
CACHE ALGORITHMtypedef struct { unsigned long tag; int data[8]; } CLine; !typedef struct { CLine entry[512 / (8 * sizeof(int)]; // 16 line } ETable; ETable cache; !bool GetData(int* adrs, int *data) { unsigned long i = (adrs & 0x000001E0) >> 5; ! if (cache.entry[i].tag == adrs & 0xFFFFFE00) { *data = cache.entry[i].data[adrs & 0x0000001F]; }
キャッシュ :512バイト ラインあたりのワード数 :8ワード ライン数 :16ライン
~キャッシュの動作をC/C++で表現してみました~
CACHE ALGORITHMtypedef struct { unsigned long tag; int data[8]; } CLine; !typedef struct { CLine entry[512 / (8 * sizeof(int)]; } ETable; ETable cache; !bool GetData(int* adrs, int *data) { unsigned long i = (adrs & 0x000001E0) >> 5; ! if (cache.entry[i].tag == adrs & 0xFFFFFE00) { *data = cache.entry[i].data[adrs & 0x0000001F]; }
・タグ :0xFFFFFE00
・インデックス :0x000001E0
・ライン内オフセット :0x0000001F
アドレスからインデックスを抽出
CACHE ALGORITHMtypedef struct { unsigned long tag; int data[8]; } CLine; !typedef struct { CLine entry[512 / (8 * sizeof(int)]; } ETable; ETable cache; !bool GetData(int* adrs, int *data) { unsigned long i = (adrs & 0x000001E0) >> 5; ! if (cache.entry[i].tag == adrs & 0xFFFFFE00) { *data = cache.entry[i].data[adrs & 0x0000001F]; }
・タグ :0xFFFFFE00
・インデックス :0x000001E0
・ライン内オフセット :0x0000001F
アドレスからタグを抽出して比較
CACHE ALGORITHMtypedef struct { unsigned long tag; int data[8]; } CLine; !typedef struct { CLine entry[512 / (8 * sizeof(int)]; } ETable; ETable cache; !bool GetData(int* adrs, int *data) { unsigned long i = (adrs & 0x000001E0) >> 5; ! if (cache.entry[i].tag == adrs & 0xFFFFFE00) { *data = cache.entry[i].data[adrs & 0x0000001F]; }
・タグ :0xFFFFFE00
・インデックス :0x000001E0
・ライン内オフセット :0x0000001F
アドレスからライン内オフセットを抽出
↑ここはちょっと変だけど許してね
Cache Entry TableCache Entry Table
Cache Entry TableCache Entry Table
タグ(上位アドレス) データタグ(上位アドレス) データタグ(上位アドレス) データ
タグ(上位アドレス) データ
N-WAY ASSOCIATEVE CACHE
nウェイアソシアティブ
キャッシュを複数(n)毎持つ
ダイレクトマップの欠点を解決
同一インデックスに対して複数エントリを持つことでヒット率向上。
アドレス000000000000000000 000000000 00000
=?Hit or Miss ?
MISS HIT!
キャッシュ内のデータをライトバック
データを読み込むためのキャッシュエントリを確保するため。
LRU/ランダム/ラウンドロビン
L2キャッシュから読み出し
読み出し終わるまでCPUはストール(パイプライン完全停止)
L2 Cache
Level 1 Cacheデータ物理アドレスデータ物理アドレスデータ物理アドレス
データ物理アドレス
LEVEL 2 CACHE
About Level 2 Cache Processor
CPU Core
L2 Cache
Mem Ctrl
DRAM
PCIe
HW…
MMU
L1 Cache
LEVEL2 CACHE SEQUENCE
Address
確定Tag
出力比較
Data出力
確定
Bus ClockL1 Cacheがアドレスを出力
L2 cacheがアドレスを確定
Tag読み出し制御
Tagが出力される
Tag比較 -> Hit !
Data読み出し制御Dataが出力される
L1 Cacheへデータを返す
読み出しに8 Clock必要
DYNAMIC RANDOM ACCESS MEMORY
About DRAM Processor
CPU Core
L2 Cache
Mem Ctrl
DRAM
PCIe
HW…
MMU
L1 Cache
外部デバイスアクセス
DRAMは外部デバイス
制御用のHWが必要
非常に低速
CPUクロックの5~10分の1
DDR3-SDRAM READ TIMING参考資料: http://www.samsung.com/global/business/semiconductor/file/product/ddr3_device_operation_timing_diagram_rev121.pdf
10 Clock@8wordバーストリード(キャッシュ1ラインフィルに相当)
DDR3-SDRAM STATE CHART
DDR3-SDRAMの内部状態遷移図
HW IPとして提供されることが多いのでこれを理解している人は少ないと思います。
重要キーワード
ModeRegister
プリチャージ
リフレッシュ
DRAMとキャッシュDRAMはキャッシュに接続される前提で機能実装
ラインフィルに必要なバーストモード
全てシングルアクセスすると致命的に遅い
要求されたデータから順にリード可能
Single Word Read = 7 Clock/Word
8 Words Burst Read = 1.25 Clock/Word (10 Clock / 8 Words)
DRAMとキャッシュは密接な関係にあります
ヒット率と アクセス時間に関する考察
Consideration Processor
CPU Core
L2 Cache
Mem Ctrl
DRAM
PCIe
HW…
MMU
L1 Cache
?
CPUの待ち時間
今までのワーストケースで1wordを読み出す時間を計算 tL1 = 1 Clock tL2 = 16 Clock (8 × 2CPUの半分のクロックで動作と過程)
tDDR3 = 28 Clock (7 × 4 CPUバスの半分のクロックで動作と過程)
tAllMiss = tL1 + tL2 + tDDR3 = 45 [Clock] CPUやシステムによって上記の値は全然違います。 実際の値を適用すると、もっとすごいことになります。
http://www.7-cpu.com/cpu/Cortex-A9.html
キャッシュヒット率を考慮キャッシュヒット率(仮定)
L1、L2ともにhit率 = 90.0%と仮定。 Avarage Access Time = tL1×0.9 + tL2 × (1 - 0.9) + tDDR3 × (1 - 0.9) × 0.9 = 4.22[clock]
バスクロックが遅いと? 同一クロックであっても外部バス速度が遅いCPUがあります。 例:Core2Duo 667MHz、Celeron 533MHz
Avarage Access Time = tL1×0.9 + (tL2 × (1 - 0.9) + tDDR3 × (1 - 0.9) × 0.9) × 667/533 = 5.05[clock] ざっくり計算です。
キャッシュやメモリ制御回路が 大部分を占めます。
実験してみよう
Study Processor
CPU Core
L2 Cache
Mem Ctrl
DRAM
PCIe
HW…
MMU
L1 Cache
?
Intel Performance Counter Monitorで実験してみましょう。
通常のループ
残念なループ
ブロッキング
アンローリング
行列計算
フォールスシェアリング
通常のループsrc[row] += dst[row][col]
dstのサイズ>>キャッシュ
キャッシュ1ラインのワード数ごとにキャッシュミスヒット。
0
1
src
9
row0 0
1
dst65535
1 0
9 65534
9 65535
row col
キャッシュ サイズ
for (unsigned long row = 0; row < ROW_SIZE; row++) { for (unsigned long col = 0; col < COL_SIZE; col++) { *(dst + row) += *(src + row * COL_SIZE + col); } }
残念なループrowのインクリメントを先にする。
インクリメント>>キャッシュ
毎回ミスヒットする。
0
1
src
9
row0 0
1
dst65535
1 0
9 65534
9 65535
row col
キャッシュ サイズ
for (int col = 0; col < COL_SIZE; col++) { for (int row = 0; row < ROW_SIZE; row++) { *(dst + row) += *(src + row * COL_SIZE + col); } }
ブロッキングキャッシュサイズの範囲内で一通り処理する。
終わったら次の領域に移る。
ミスヒットが格段に減る。
0
1
src
9
row0 0
1
dst65535
1 0
9 65534
9 65535
row col
キャッシュ サイズ
for (int Blk = 0; Blk < COL_SIZE; Blk += BLKSIZE) { for (int row = 0; row < ROW_SIZE; row++) { for (int col = Blk; col < Blk + BLKSIZE; col++) { *(dst + row) += *(src + Blk * BLKSIZE + col); } } }
ブロックサイズ
ループアンローリングループ処理を展開して分岐命令を削減
展開しすぎるとプログラムが大きくなり、命令キャッシュミスが増える
for (unsigned long row = 0; row < ROW_SIZE; row++) { for (unsigned long col = 0; col < COL_SIZE;) { *(data_dst + row) += *(data_src + row * COL_SIZE + col++); *(data_dst + row) += *(data_src + row * COL_SIZE + col++); *(data_dst + row) += *(data_src + row * COL_SIZE + col++); …略… *(data_dst + row) += *(data_src + row * COL_SIZE + col++); } }
処理 インクリメント 分岐判定処理 インクリメント 分岐判定
処理 インクリメント 分岐判定
処理 インクリメント
処理 インクリメント
処理 インクリメント
……
実行命令数が減る =処理時間も減る
プログラムサイズは 増える
行列計算
普通にアルゴリズムを書くと、先ほどの「普通のループ」と「残念なループ」の組み合わせになる。
片方をわざと行列を入れ替えて定義する
int A[row][col]; int B[col]row];
int A[row][col]; int B[row][col];
フォールスシェアリング
複数のスレッドが同一キャッシュラインのデータにライトアクセスするときに発生し得る問題。
Core1 Core2
Cache Line Cache Line
DRAM
Core 1とCore2は異なるアドレスにライトする。
アドレスは異なるが、キャッシュラインは同一。
Core1がデータを書き換える。
他コアもキャッシュしているため、DRAMにライトバック。
Core2はDRAMとキャッシュが不一致のため、キャッシュされていたデータは「無効(データ無し)」とする。
Core2がデータを書き換える。
ミスヒットが発生し、DRAMからデータを読み出す。
フォールスシェアリング解決策
複数のスレッドで共有する必要の無いデータはアドレスが64バイト以上離れるようにする。
Core1 Core2
Cache Line Cache Line
DRAM
void worker_thread1(void) { for (int i = 0; i < MAX; i++) dstArea[0] += srcArea[i]; } !void worker_thread2(void) { for (int i = 0; i < MAX; i++) dstArea[1] += srcArea[i]; } !void worker_thread3(void) { for (int i = 0; i < MAX; i++) dstArea[64] += srcArea[i]; }
thread1と2はフォールスシェアリング thread1と3は無し
実験結果:ループ「残念なループ」はキャッシュヒット率が低く、DRAMアクセスが多い。
「ブロッキング」はDRAMアクセスが少ない。
簡単なプログラムのため差が出にくかったかも。
テストプログラム以外のプロセスのカウント値も含まれていると思われます。
時間 L1 HIT DRAM READ
普通 1.68 99.82 5542MB
残念 14.09 89.54 41100MB
ブロッキング
1.63 99.95 283MB
アンローリング
1.62 99.91 5460MB
実験結果:行列計算/シェアリング
行列を入れ替えるとキャッシュヒット率が向上しDRAMアクセスが減る。
フォールスシェアリングを回避することによりDRAMアクセスが半分になっている。
「ループ」、「行列計算」、「シェアリング」はそれぞれ異なるテストプログラムを実行しています。
時間 L1 HIT DRAM READ
通常 13.33 92.30 49.8GB
行列入れ替え
2.06 99.75 10.7GB
時間 L1 HIT DRAM READ
FALSE SHARE 10.54 95.42 926MB
NONE SHARED 4.62 98.77 421MB
参考資料アーキテクチャ全般
David A.Patterson/John L.Hennessy; パターソン&ヘネシー コンピュータの構成と設計 第4版,
日経BP社, 2011年.
中森 章; マイクロプロセッサ・アーキテクチャ入門, CQ出版, 2004年.
CPU実例
ARM
Cortex-A9 テクニカルリファレンスマニュアル, r2p2
CoreLink Level 2 Cache Controller L2C-310 Technical Reference Manual, r3p3
Intel
Intel® 64 and IA-32 Architectures Software Developer's Manual Volume 3A: System Programming Guide, Part 1
Intel® 64 and IA-32 Architectures Optimization Reference Manual
Performance Analysis Guide for Intel® Core™ i7 Processor and Intel® Xeon™ 5500 processors