demand miss rate とは software prefetch のことらしい。しらなんだ。
よく考えたら demand するのはコード書いた人だし、分かる気もしてきた。
最近換気したらすっと体調がよくなって驚いたので、換気はそこそこ重要なんだと気づいた。 たくさんするのもめんどいし、良きタイミングで換気を促す仕組みとして何か無いかなと考え、二酸化炭素濃度計測計を買ってみた。換気の要因は二酸化炭素濃度を減らすだけではなさそうだけれど…。9000円した。
冬なんかは部屋をよく締め切っていたのだけど、そうしてみると二酸化炭素濃度がモリモリあがっていく様子が見える。自分がたしかに締め切ってた間はちょっと高めになっていたかも知れない。
部屋は少し古めだけれど、24時間換気の仕組みはついている。そのため部屋を締め切っても、自分が部屋を出ていると二酸化炭素濃度は低下していた。 24時間換気の吸気口は部屋に一つついており、部屋のドアの下部の隙間を抜け、さらに洗面台の前のドアの下部の隙間を抜けた先に排気口があるので、この仕組みの換気はいずれかの通り道で律速しているのか、換気により減少する速度と自分の呼気により増加する量を比べると呼気の量が多くなっていそうだった。 そこで、部屋のドアを開けてみると増加は抑えられて安定するようになった。
あと、炭酸の抜けてない炭酸飲料を計測計の近くにおいておくと濃度が急上昇して、ちょっと実験みがあった。
recon_trace の話、は既に検索すると沢山出てくるのであんまり出てこなかった tips を。
recon_trace:calls
の戻り値は trace 対象となった関数の数が返るが、対象の関数が一つもなかった場合は 0 が返る。
typo もなく正しく与えたはずなのにうまく trace 対象とならない場合があり、その時は未ロードの場合に起きうる。
その時は Module:module_info(module)
とか叩くと強制的にロードされるので、その後に trace 出来るようになる。
trace 時に recon_trace:calls({M, F, fun(_) -> return_trace() end}, 10)
などすると、関数の呼び出し時 trace だけでなく、関数の戻り値も trace してくれるようになる。すると 2回 trace することになる。
recon_trace:calls([{ModuleA, F, 0}, {ModuleB, F, 0}], 10)
のようにする。
$ recon_trace:calls({ModuleA, F, 0}, 10).
$ recon_trace:calls({ModuleB, F, 0}, 10).
のようにしても ModuleB の trace しかされない。おそらく対象は trace の度にリセットされている。
“what every programmer should know about memory”, 6.3 の prefetch の章を読み終わった。
hardware prefetch がなかなか面白くて、 stream を扱うバッファがいくつかあり、単純なアクセスやストライドアクセスなどの アクセスパターンを stream とみなして処理しているのを見て良く出来てるなーと関心してしまった。
これを見て考えていた問題が解決したように思えた。というのもペーパーの中で行列の掛け算を高速化する話があり、以下のようなコードが出てきて
// straight forward
for(i=0;i<N;i++)
for(j=0;j<N;j++)
for(k=0;k<N;k++)
r[i][j] += a[i][k] * b[k][j];
// transpose
for(i=0;i<N;i++)
for(j=0;j<N;j++)
t[i][j] = b[j][i]
for(i=0;i<N;i++)
for(j=0;j<N;j++)
for(k=0;k<N;k++)
r[i][j] += a[i][k] * t[j][k];
このコードはどちらも計算結果を r 配列に収めようとしていて、素直な方法と b 配列を転置してから計算する方法があり、transpose するほうが早いという話だった。実際 LLC が 4MB ほどの Core i5 MBP で計算してみると straight forward なほうが実行に 2.5 秒、transpose が 1.5 秒程となりちょっと驚いた。
transpose は処理が多いものの、実際の再内ループでは cache line を無駄なく使うようになっていて早いという話が書いてあった気がする。あーこの配列は double の配列なので、各要素は 8 byte となる。最近の cpu は大体 cache line が 64 byte になっており、要素がすべて隣接している場合は 8 要素 cache line に含まれる形になる。
しかしながら straight forward の再内ループに注目すると a[i][k] は cache line 境界をまたがない限りは同じ cache line に含まれそうだが b[k][j] はそうではなく b[k+1][j] となり cache line のうち 8 byte しか使えずかなり富豪的な使いかたをしてしまっている。
ところが、LLC がもっと大きい 12MB とか 16MB とかの cpu で同じように計算してみると straight forward と transpose の実行時間の差は見られなくなってしまった。これはなぜだ…と考えていたが、cache line を無駄使いするものの hardware prefetch 自体は stride を認識して問題なく動いていたためではなかろうかと考えられた。
そのつぎの Direct Cache Access も面白かった。パケット処理などの技術はこれらに支えられているんだなと。
あとこの本だと Core2 の時期なのでメモリコントローラがチップセット上の LSI にあるらしく FSB とその帯域の話がよく出てくるが今はもう使われていないため、昔は大変だったんだなという感想だった。
Ryzen の cache misses をみたくなったがらみれずに悪戦苦闘していた…。 perf でさくっと取れない。
likwid という tool の bench だと perf で取れない cycles 数やリタイアした命令数も取れているので、なんとか取れないもんか…。