本文已参与「新人创作礼」活动,一起开启掘金创作之路。
以如下矩阵乘法为例解释分块乘法可以有效利用cache。
设:
- 如下两个8∗8的矩阵A,B,按4∗4进行分块乘法。
- Cache有12行,每行可以存放4个Int。(目的是使得cache虽然不能装下整个矩阵,但是能装下3个分块矩阵,其中两个是做乘法的矩阵,第三个是结果矩阵)
- 缓存不命中次数初始值n=0
A=⎣⎡0000000000000000000000000000000000000000000000000000000000000000⎦⎤B=⎣⎡0000000000000000000000000000000000000000000000000000000000000000⎦⎤
以A11∗B11为例(分别是A和B的左上矩阵)
- 首先是A11的第一行乘B11的第一列:先计算A[0][0]∗B[0][0],这时A11,B11缓存均不命中,将相应块读入缓存(标红元素表示读入缓存):
A=⎣⎡0000000000000000000000000000000000000000000000000000000000000000⎦⎤B=⎣⎡0000000000000000000000000000000000000000000000000000000000000000⎦⎤
n=2
- 接下来A[0][1]∗B[1][0]直到A[0][3]∗B[3][0],A11均命中而B11不命中:
A=⎣⎡0000000000000000000000000000000000000000000000000000000000000000⎦⎤B=⎣⎡0000000000000000000000000000000000000000000000000000000000000000⎦⎤
n=5
- 接下来A11的第一行依次乘B11的二、三、四列,由于上一次循环已经把整个B11缓存进来了,因此全部缓存命中:
A=⎣⎡0000000000000000000000000000000000000000000000000000000000000000⎦⎤B=⎣⎡0000000000000000000000000000000000000000000000000000000000000000⎦⎤
n=5
- 然后A11的第二行依次乘B11所有列,在计算A[1][0]∗B[0][0]时A11缓存不命中,需要读入缓存:
A=⎣⎡0000000000000000000000000000000000000000000000000000000000000000⎦⎤B=⎣⎡0000000000000000000000000000000000000000000000000000000000000000⎦⎤
n=6
A=⎣⎡0000000000000000000000000000000000000000000000000000000000000000⎦⎤B=⎣⎡0000000000000000000000000000000000000000000000000000000000000000⎦⎤
n=8
总结分析
设A,B为n∗n的矩阵,按b∗b进行分块,那么一个矩阵可以划分为 (n/b)∗(n/b) 个分块矩阵,缓存块=4ints,Cache的容量C<<n(一行都放不下),3b2<C(可以放下三个分块矩阵),那么每一块的不命中次数为 b2/4(平均每4个元素发生一次不命中,因为一个缓存块可以放4个元素,一个分块矩阵每行每列为 b 个元素),每一次迭代(一次迭代为一整行的分块矩阵乘一整列的分块矩阵)总不命中次数=2n/b∗b2/4=nb/2,n/b表示每行或每列的分块矩阵数,乘2表示两个做运算的矩阵分别不命中),整个矩阵乘法下来总不命中次数=nb/2∗(n/b)2=n3/(2b)。
结合上面的例子,每一块的不命中次数为42/4=4,每一次迭代不命中数为8∗4/2=16次,整个矩阵乘法的不命中次数为83/(2∗4)=64,命中率为1−64/(2∗83)=93.75%。
而如果不使用分块乘法,按照最简单的计算方式,总不命中次数为5/4∗83=640,命中率为1−640/(2∗83)=37.5%。
上述例子不太严谨,因为整个Cache是可以放下矩阵的一行的,但是基本原理还是能体现个大概的。