L3キャッシュミス ア‐ラ‐カルト
L3キャッシュミスを計測するプログラムを書いてみました。しかし、一口にL3キャッシュミスと言っても複数のイベントがあります。状況に応じて適切なイベントを選択できるようにそれぞれのイベントの意味を整理しておこうという趣旨のメモです。
今回は6種類のイベントを考えてみます。6種類のイベントを下の表にまとめておきます。Intelの技術資料を日本語訳したものです。
イベント名 | Event Num | Umask Value | Description |
---|---|---|---|
MEM_LOAD_RETIRED.L3_MISS | 0xCB | 0x10 | L3キャッシュにミスしリタイアしたLOAD命令の数を示す。このLOAD命令にはリモートソケット、ローカルメモリ、IOHからのLOAD命令を含む。 |
L3_LAT_CACHE.MISS | 0x2E | 0x41 | このイベントはL3キャッシュへの参照ミスの回数をカウントする。このイベントは投機的な通信を含むが、L2ハードウェアプリフェッチによるキャッシュラインフィルを除く。キャッシュ階層、つまりはキャッシュサイズとその他の実装依存の特徴により、このイベントをパフォーマンスの違いを推定する際の比較に用いることは推奨できない。 |
UNC_L3_MISS.READ | 0x09 | 0x01 | L3キャッシュにミスしたコードreadとデータread、そしてRFO(Request For Ownership)リクエストの回数を示す。 |
UNC_L3_MISS.WRITE | 0x09 | 0x02 | レジスタのデータをメモリに書き出す際の、L3キャッシュにミスした回数を示す。インクルーシブキャッシュ方式の利点が効いている限りL3キャッシュへのライトバックはL3キャッシュにヒットするのでこの値は0になるだろう。 |
UNC_L3_MISS.ANY | 0x09 | 0x03 | L3キャッシュのREADキャッシュミスとWRITEキャッシュミスの回数を示す。 |
UNC_L3_MISS.PROBE | 0x09 | 0x04 | IOHとリモートソケットがL3キャッシュにミスしスヌープした回数を示す。 |
上のイベントを計測するプログラムを作成し、さらに計測中にシステムに負荷をかけるプログラムを走らせて実験を行ってみます。
環境:Linux debian 2.6.32-5-amd64 [debian squeeze]
:Intel(R) Xeon(TM) CPU L5520 @ 2.26GHz
イベントを計測するプログラムはl3missというプログラムで、msrライブラリを使用しています。今回はキャッシュミスなので、負荷をかけるプログラムは以前にこのブログで書いたImageMagickライブラリ+Pthreadを使ったプログラムを走らせます。img_processというプログラムです。これらはシェルスクリプトで制御することとします。
#!/bin/bash modprobe msr ./l3miss & sleep 10 ./img_process -N 10 sleep 10 kill -15 `cat l3miss.pid`
*実験結果
l3missは1秒ごとにイベントを計測して、前回との差分を計算し、最後にCSVファイルを出力してくれるので、Excelでグラフ化しました。横軸は時間、縦軸はイベントの1秒間の変化量です。
まずはCOREイベントの2つです。今回はHTTを無効にしていますので、4コアで4本のグラフがあります。
次はUNCOREイベントの4つです。MSR_UNCORE_PMCxはレジスタのスコープがPackageなのでグラフはCPUソケット分です。今回は使用したマシンは1基のCPUしか搭載されてないのでグラフは1本です。
*考察
・各グラフの違いに関して
まずCOREイベントに関してですが、MEM_LOAD_RETIRED.L3_MISSはLOAD命令(メモリからレジスタにデータを格納する命令)で、リタイアしたものをカウントしています。L3_LAT_CACHE.L3_MISSはL3キャッシュへ参照した回数です。なので、ソフトウェアの参照だけでなく、ハードウェアの参照の数も含まれています。そのためL3_LAT_CACHE.L3_MISSはMEM_LOAD_RETIRED.L3_MISSより全体的に数値が大きくなっています。
次にUNCOREイベントに関してです。UNC_L3_MISS.READは「データの読み込み要求があったが、キャッシュラインにデータが無かった」という事象をカウントするイベントです。それに対し、UNC_L3_MISS.WRITEは「データの書き込み要求があったが、キャッシュラインに要求アドレスのデータが無かった」という事象をカウントしています。そして、UNC_L3_MISS.ANYは両方のイベントの発生回数を単純に足しているイベントです。
最後にUNC_L3_MISS.PROBEですが、これはIOHからのスヌープの回数です。スヌープとはデータが無いかを確認する作業を指しますが、今回使用したマシンはCPUが1基しか搭載されていませんので、IOHからのスヌープの回数のみが計測されていることになります。
IOHからのスヌープがどのような状況で起こるか、ですが、これはDMA転送時に起こります。そして今回実はUSBメモリのバイナリを直接実行しています。そして画像の入出力先のUSBメモリです。DMA転送時にはQPIというインターフェースがスヌープ要求を制御しているのでこの回数がカウントされているのだと思われます。NehalemでのDMA転送時のフローを説明されているドキュメントが下記にあります。
http://www.hotchips.org/archives/hc21/2_mon/HC21.24.200.I-O-Epub/HC21.24.230.DasSharma-Intel-5520-Chipset.pdf
・img_processの挙動に関して
今回はimg_processを10スレッド、つまり1スレッド画像1枚で合計10枚分の画像処理を行っています。スレッドで並列で処理されるのですが、190秒〜200秒の間で6枚の画像が出力され、237秒に1枚、303秒と308秒に2枚の画像が、そして最後が401秒に出力されていました。このことはUNC_L3_MISS.READのグラフが非常によく表しています。
スレッドが完了するごとにキャッシュミスも階段状に減っていっています。そしてMEM_LOAD_RETIRED.L3_MISSのグラフから最後に処理が完了したスレッドはCPU#0で実行されたのだろうと判断できます。さらに、スレッドが終了するタイミングでUNC_L3_MISS.WRITEのグラフからWRITEキャッシュミスが発生していることも分かります。おそらく画像のヘッダ情報とか、ライブラリの終了関数とかを呼び出す時にミスしているのでしょう。ただ、RFO(Request For Ownership)により、WRITEキャッシュミスは本質的にREADキャッシュミスでもある*1ため、UNC_L3_MISS.READのグラフもスレッドが終了するタイミングで一瞬上がっています。
*まとめ
「どんな時にどのイベントを使うのがいいのか?」という当初の趣旨に立ち返ってみます。
論理CPUごと、ringごとの解析をしたいなら、COREイベントを選ぶべきです。一方、システム全体でのキャッシュ効率を見たいならUNCOREイベントがいいと思います。ただUNCOREイベントはNehalemの場合、Xeonの5500番台か7500番台でしかサポートされてないようなので、いつでも使えるとは限りません。
COREイベントに関しては
- 例えば、自分が作ったプログラムがある特定のプラットフォームでのキャッシュ効率を調べたいならMEM_LOAD_RETIRED.L3_MISSを使うべきです。
- 例えば、L3キャッシュミスのDecompositionをやりたい、分岐予測や、プリフェッチなどと絡めて解析したい、などの要件がある場合は全体を表すデータとしてL3_LAT_CACHE.MISSが有効だと思います。
UNCOREイベントに関しては
- キャッシュミスは本質的にREADキャッシュミスに集約されるので、UNC_L3_MISS.READを調べると、そのCPUにおけるL3キャッシュミスの回数を可視化できます。
- WRITEキャッシュミスの回数を調べたい場合はUNC_L3_MISS.WRITEを使うべきです。
- 複数のCPUパッケージが実装されているマシンでUNC_L3_MISS.PROBEを計測するとCPU間の通信を可視化することができます。
- UNC_L3_MISS.ANYはあまり意味が無い(と思う)。
*1:キャッシュをバイパスするとなぜ速くなるのか? http://d.hatena.ne.jp/hyoshiok/20051101