利用分块矩阵计算矩阵乘法可以有效利用Cache

301 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

以如下矩阵乘法为例解释分块乘法可以有效利用cache。 设:

  • 如下两个888 *8的矩阵A,BA,B,按444*4进行分块乘法。
  • Cache有12行,每行可以存放4个Int。(目的是使得cache虽然不能装下整个矩阵,但是能装下3个分块矩阵,其中两个是做乘法的矩阵,第三个是结果矩阵)
  • 缓存不命中次数初始值n=0n=0
A=[0000000000000000000000000000000000000000000000000000000000000000]B=[0000000000000000000000000000000000000000000000000000000000000000]A=\left[ \begin{array}{cccc|cccc} 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \hline 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \end{array} \right] \quad B=\left[ \begin{array}{cccc|cccc} 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \hline 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \end{array} \right]

A11B11A_{11}*B_{11}为例(分别是AABB的左上矩阵)

  • 首先是A11A_{11}的第一行乘B11B_{11}的第一列:先计算A[0][0]B[0][0]A[0][0]*B[0][0],这时A11B11A_{11},B_{11}缓存均不命中,将相应块读入缓存(标红元素表示读入缓存):
A=[0000000000000000000000000000000000000000000000000000000000000000]B=[0000000000000000000000000000000000000000000000000000000000000000]A=\left[ \begin{array}{cccc|cccc} \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \hline 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \end{array} \right] \quad B=\left[ \begin{array}{cccc|cccc} \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \hline 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \end{array} \right]

n=2n=2

  • 接下来A[0][1]B[1][0]A[0][1]*B[1][0]直到A[0][3]B[3][0]A[0][3]*B[3][0]A11A_{11}均命中而B11B_{11}不命中:
A=[0000000000000000000000000000000000000000000000000000000000000000]B=[0000000000000000000000000000000000000000000000000000000000000000]A=\left[ \begin{array}{cccc|cccc} \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \hline 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \end{array} \right] \quad B=\left[ \begin{array}{cccc|cccc} \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \hline 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \end{array} \right]

n=5n=5

  • 接下来A11A_{11}的第一行依次乘B11B_{11}的二、三、四列,由于上一次循环已经把整个B11B_{11}缓存进来了,因此全部缓存命中:
A=[0000000000000000000000000000000000000000000000000000000000000000]B=[0000000000000000000000000000000000000000000000000000000000000000]A=\left[ \begin{array}{cccc|cccc} \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \hline 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \end{array} \right] \quad B=\left[ \begin{array}{cccc|cccc} \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \hline 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \end{array} \right]

n=5n=5

  • 然后A11A_{11}的第二行依次乘B11B_{11}所有列,在计算A[1][0]B[0][0]A[1][0]*B[0][0]A11A_{11}缓存不命中,需要读入缓存:
A=[0000000000000000000000000000000000000000000000000000000000000000]B=[0000000000000000000000000000000000000000000000000000000000000000]A=\left[ \begin{array}{cccc|cccc} \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \hline 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \end{array} \right] \quad B=\left[ \begin{array}{cccc|cccc} \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \hline 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \end{array} \right]

n=6n=6

  • A11A_{11}后两行同理:
A=[0000000000000000000000000000000000000000000000000000000000000000]B=[0000000000000000000000000000000000000000000000000000000000000000]A=\left[ \begin{array}{cccc|cccc} \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \hline 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \end{array} \right] \quad B=\left[ \begin{array}{cccc|cccc} \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \red{0} & \red{0} & \red{0} & \red{0} & 0 & 0 & 0 & 0 \\ \hline 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \end{array} \right]

n=8n=8

总结分析

ABA,Bnnn*n的矩阵,按bbb*b进行分块,那么一个矩阵可以划分为 (n/b)(n/b)(n/b) * (n/b) 个分块矩阵,缓存块=4ints=4\quad ints,Cache的容量C<<nC << n(一行都放不下),3b2<C3b^2<C(可以放下三个分块矩阵),那么每一块的不命中次数为 b2/4b^2/4(平均每4个元素发生一次不命中,因为一个缓存块可以放4个元素,一个分块矩阵每行每列为 bb 个元素),每一次迭代(一次迭代为一整行的分块矩阵乘一整列的分块矩阵)总不命中次数=2n/bb2/4=nb/2=2n/b*b^2/4=nb/2n/bn/b表示每行或每列的分块矩阵数,乘2表示两个做运算的矩阵分别不命中),整个矩阵乘法下来总不命中次数=nb/2(n/b)2=n3/(2b)=nb/2*(n/b)^2=n^3/(2b)

结合上面的例子,每一块的不命中次数为42/4=44^2/4=4,每一次迭代不命中数为84/2=168*4/2=16次,整个矩阵乘法的不命中次数为83/(24)=648^3/(2*4)=64,命中率为164/(283)=93.75%1-64/(2*8^3)=93.75\%

而如果不使用分块乘法,按照最简单的计算方式,总不命中次数为5/483=6405/4*8^3=640,命中率为1640/(283)=37.5%1-640/(2*8^3)=37.5\%

上述例子不太严谨,因为整个Cache是可以放下矩阵的一行的,但是基本原理还是能体现个大概的。