几种通过降低Cache失效率来提升程序性能的方法

几种通过降低Cache失效率来提升程序性能的方法

​ 当程序访问多个数组时,经常会出现有些数组按行访问,有些数组按列访问的情况。以矩阵的乘法为例, C = A × B C=A\times B C=A×B ,经典的计算矩阵乘法的算法如下:

void mult() {
    for(int i=0;i<N;i++) {
        for(int j=0;j<N;j++) {
            double res=0.0;
            for(int k=0;k<N;k++) {
                res += (matrix_a[i][k]*matrix_b[k][j]);
            }
            matrix_c[i][j] = res;
        }
    }
}

​ 可以看出,内部的两个循环 j j j , k k k, 将反复读取矩阵 m a t r i x _ b [ N ] [ N ] matrix\_b[N][N] matrix_b[N][N] 的全部的 N × N N\times N N×N 个元素,以及反复读取矩阵 m a t r i x _ a [ N ] [ N ] matrix\_a[N][N] matrix_a[N][N] 的全部的第 i i i 行的 N N N 个元素,所产生的结果 r e s res res 将存入至结果矩阵 m a t r i x _ c [ N ] [ N ] matrix\_c[N][N] matrix_c[N][N] 的第 i i i 行。

​ 考虑此时 Cache 的失效情况,抛开 Cache 大小无限制或者 Cache 的大小完全能装下这三个数组的最为理想的情况。考虑对三个数组访问时,导致 Cache 失效的情况,最坏的情况是,每次的访问都失效,这是会导致共 2 N 3 + N 2 2N^3 + N^2 2N3+N2 次的失效。为了减少失效,下面有两种方法能显著的减小 Cache 的失效率。


1 将矩阵转置

void mult() {
    int i,j,k;
    for(i=0;i<N;i++) {
        for(j=0;j<N;j++) 
        matrix_c[j][i]=matrix_b[i][j];
    }
    for(int i=0;i<N;i++) {
        for(int j=0;j<N;j++) {
            double res=0.0;
            for(int k=0;k<N;k++) {
                res += (matrix_a[i][k]*matrix_c[j][k]);
            }
            matrix_c[i][j] = res;
        }
    }
}

毫无疑问的是,转置矩阵也是一个耗时的过程,但在转置处花费的时间和后面计算矩阵的乘积结果所节省的时间达到了很好的折中,下面是测试图:

(矩阵规模是 1025 × 1025 1025\times 1025 1025×1025,生成矩阵的方法是生成随机的 double 类型的浮点数 )

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fR1o9FV4-1607694187293)(E:\AI\ai_hmwk\compare.png)]

​ 可以看到,转置操作再加上转置后的矩阵乘的开销远远小于直接矩阵乘的开销。

​ 其中最主要的原因就是,转置后的矩阵乘法,最内部的循环 k k k ,都将反复读取矩阵 m a t r i x _ a [ N ] [ N ] matrix\_a[N][N] matrix_a[N][N] 的全部的第 i i i 行的 N N N 个元素,以及矩阵 m a t r i x _ b [ N ] [ N ] matrix\_b[N][N] matrix_b[N][N] 的全部的第 j j j 行的 N N N 个元素,而这样的读取方式,很好地利用了空间局部性,让对于程序员透明的 Cache 发挥了重要的作用。


2 分块处理矩阵乘法

​ 分块是一种经典的利用 Cache 来提升程序性能的技术。分块算法不是对数组的整行或者整列进行访问的,而是把对大数组的访问分解成对子矩阵的访问。

​ 为了保证正在访问的元素能在 Cache 中命中,把原程序内部改为仅仅对大小为 B × B B\times B B×B 的子数组进行计算,其中 B B B 称为分块因子,代码如下:

void multB() {
    int jj,kk,i,j,k;
    double r;
    for(jj=0;jj<N;jj+=B)
    for(kk=0;kk<N;kk+=B)
    for(i=0;i<N;i++)	//处理 BxB 大小的子矩阵
    for(j=jj;j<Min((jj+B),(N));j++){
        r=0.0;
        for(k=kk;k<Min((kk+B),(N));k++)
            r+=(matrix_a[i][k]*matrix_c[k][j]);
        matrix_c[i][j]+=r;
    }
}

​ 这时再以开始的条件考虑失效次数,可以初步判断 Cache 的失效率会降低,测试结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f10GyOD2-1607694187295)(E:\AI\ai_hmwk\cp2.png)]


3 将分块和转置结合

代码如下:

void mult_T_and_B() {
    int jj,kk,i,j,k;
    double r;
    for(i=0;i<N;i++) {
        for(j=0;j<N;j++) 
        matrix_c[j][i]=matrix_b[i][j];
    }
    for(jj=0;jj<N;jj+=B)
    for(kk=0;kk<N;kk+=B)
    for(i=0;i<N;i++)
    for(j=jj;j<Min((jj+B),(N));j++){
        r=0.0;
        for(k=kk;k<Min((kk+B),(N));k++)
            r+=(matrix_a[i][k]*matrix_c[j][k]);
        matrix_c[i][j]+=r;
    }
}

测试时间如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EYOxI1rA-1607694187296)(E:\AI\ai_hmwk\cp3.png)]


4 总结

程序的平均访存公式如下:
平 均 访 存 时 间 = 命 中 时 间 + 失 效 率 × 失 效 开 销 平均访存时间=命中时间+失效率\times 失效开销 访=+×

  • 以上讨论的提升程序性能的方法的根本就是在降低失效率

  • 虽然Cache 对程序员而言是透明的,但深入理解 Cache 的基本原理,能在不经意间提高程序的性能。


  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值