ブロッキングサイズの改良|行列積高速化#26
当初の設計(下記の記事)では、I,J,Kループのキャッシュブロッキングサイズは、キャッシュメモリの容量に収まるように決めました。
しかし、L1キャッシュブロッキングの有無がカーネル関数の性能を左右していたため、性能実測しながらL1~L3のブロッキングサイズを変更することにしました。前回の記事のカーネル関数の性能は、ブロッキングサイズ改良後の測定値になります。
改良したブロッキングサイズ
当初の設計では、キャッシュメモリのブロッキングサイズを下記のように設定していました。
#define MYBLAS_PANEL_M 256
#define MYBLAS_PANEL_N 256
#define MYBLAS_PANEL_K 256
#define MYBLAS_BLOCK_M 128
#define MYBLAS_BLOCK_N 64
#define MYBLAS_BLOCK_K 128
#define MYBLAS_TILE_M 32
#define MYBLAS_TILE_N 32
#define MYBLAS_TILE_K 32
改良後のブロッキングサイズは次のようになりました。
#define MYBLAS_PANEL_M 2048
#define MYBLAS_PANEL_N 2048
#define MYBLAS_PANEL_K 2048
#define MYBLAS_BLOCK_M 512
#define MYBLAS_BLOCK_N 512
#define MYBLAS_BLOCK_K 256
#define MYBLAS_TILE_M 16
#define MYBLAS_TILE_N 16
#define MYBLAS_TILE_K 128
今回、性能測定は2048x2048行列積までしか行っていないので、L3キャッシュ用ブロッキングサイズ(MYBLAS_PANEL_x)は、ブロッキングしていないのと同等です。結果的に、L3キャッシュ向けにはブロッキングが不要だったと言えそうです。
L2キャッシュ向けのブロッキングサイズ(MYBLAS_BLOCK_x)は想定よりも大きいサイズになりました。行列Aが512x256x8B=1MB、行列Bも512x256x8B=1MB、行列Cは512x512x8B=2MBになるので、合計4MBを使用している計算になります。4MBはL3キャッシュの全容量に等しいので、L2向けのブロッキングループでL3キャッシュ用ブロッキングを行っていると考えられます。
L1キャッシュ用のブロッキングサイズ(MYBLAS_TILE_x)は、K方向の数値を大きくした方がより高速になりました。これは、行列積カーネル関数で、K方向を最内ループにしているためだと考えられます。
最深ループは、外側からN-M-Kの三重ループにし、Kループが終わったあと行列Cへの書き込みを行います。そのため、Kループが短いと行列Cへの書き込み回数が増え、逆にKループが長いと行列Cへの足し込みか数が減少します(代わりにレジスタへの足し込みが増えます)。当然、レジスタ上で計算してしまった方が高速なので、Kループは長い方が良いということでしょう。
L1用ブロッキングサイズは、行列Aと行列Bが16x128x8B=16KB、行列Cは16x16x8B=2KBと、合計34KBになっています。行列Cは、行列Aや行列Bに比べると、ロード頻度は少ない代わりにストアが必要になります。このことから、高頻度にロードされる行列Aと行列Bだけで、L1キャッシュサイズの32KBを占有することを考え、行列CはL2キャッシュに保管されていると考えれば良さそうです。
以上を総合すると、L2キャッシュはほとんど意識しなくて良いのかもしれません。
カーネル速度の比較
下記は、改良前のカーネル関数に関する、L1ブロッキングサイズの違いによる性能の違いになります。
当初のL1ブロッキングサイズ32x32x32(青線)の場合、カーネル関数の性能は69%程度でした。8x8x128(オレンジ線)にしてもほとんど変わりませんでしたが、16x16x64(灰色線)にしたところ、5%ほど性能向上が見られました。16x16x128(黄色線)にすると、さらに5%ほど性能が向上しています。
しかし、16x16x256(水色線)では急に性能が落ちてしまいました。これは、行列Aと行列Bのブロック容量が64KBになり、L1キャッシュサイズ32KBを超えてしまったためだと考えられます。
以上の結果から、L1キャッシュブロッキングサイズは、16x16x128に変更しました。
まとめ
実測値から、キャッシュブロッキングサイズを変更しました。
結果として、L2キャッシュブロッキングはあまり意識する必要はありませんでした。
L2キャッシュブロッキング用のブロッキングサイズはL3キャッシュサイズに合わせる必要がありました。そのため、用意したL3キャッシュブロッキングは不要と考えられます。
L1キャッシュブロッキングは、K方向に長い方が高速でした。また、行列Aと行列BのブロックでL1キャッシュを占有する設定の方が高速でした。行列Cは、L1キャッシュ上にないものと考えて良いようです。
次の記事
ソースコード