《CS:App》实验 Cache Lab 配彩图 详细解析

《CS:App》实验 Cache Lab 解析

准备

前置介绍、准备

1.《CS:App》官网:http://csapp.cs.cmu.edu/3e/labs.html
在这里插入图片描述
WriteUp中的pdf需要好好看,里面有详细的规则和建议。

2.Blocking 分块技术http://csapp.cs.cmu.edu/2e/waside/waside-blocking.pdf
这是PartB中需要用到的技术。

3.Valgrind的导出文件
PartA使用Valgrind的导出文件作为模拟器的输入。其内容为

#yi.trace
I xxxx
 L 10,1
 M 20,1
 L 22,1
 S 18,1
 L 110,1
 L 210,1
 M 12,1

I => Instruction L => Load S => Store M => Modify

operationaddresssize
Load(data)101
Modify(data)201
Load(data)221
Store(data)181

其中LS只访问一次Cache;M会先LS,访问两次Cahce,同时其第二次访问必命中。
还有I指令,这里没有列出。它表示指令加载,这里我们不做处理,过滤即可。因为这里的Cache我们只存数据,不存指令。

实验总述

该实验分为两部分
Part A: Writing a Cache Simulator 实现一个Cache模拟器。
Part B: Optimizing Matrix Transpose 实现并优化一个矩阵转置函数。

Part A: Writing a Cache Simulator

任务描述

1.任务
 在csim.c中使用LRU算法完成Cache模拟器的代码编写,并与核验程序csim-ref的执行结果(hits misses evictions数)相同。该模拟器以Valgrind的导出文件作为输入。

linux> ./csim-ref -v -s 4 -E 1 -b 4 -t traces/yi.trace 
L 10,1 miss
M 20,1 miss hit 
L 22,1 hit
S 18,1 hit
L 110,1 miss eviction
L 210,1 miss eviction
M 12,1 miss eviction hit 
hits:4 misses:5 evictions:3

2.规则

  • 代码内不能出现Warning,否则编译失败
  • 将Hits Misses Evictions 传给printSummary()来显示
  • 因为需要指定s E b,所以需要使用malloc动态分配
  • 本实验假定内存访问已正确对齐,像size=1/4/8都可以存在一行Cache中,也同时读取这么多,所以我们可以忽略trace文件中的size

分析

 分析好需求+明确LRU、I/L/S/M指令的定义,应该就能写出来了,难度像学生管理系统^^。

实现

 因为代码较长,CSDN还不能折叠。就放在网盘了。
PartA PartB C文件-微云
PartA PartB C文件-CSDN免积分最终结果

Part B: Optimizing Matrix Transpose

任务描述

1.任务
 编写一个Cache (s=5,E=1,b=5)友好的转置函数。在trans.c文件中补全transpose_submit()函数,以尽可能少的miss数完成矩阵A->矩阵B的转置,即A[m][n]->B[n][m]。
trans.c中已提供一个简单的转置函数,可是它有很多的miss。在这里插入图片描述

2.规则

  • 不能有Warning,否则无法通过编译
  • 每一个转置函数最多使用12个int型局部变量,所有的函数使用的局部变量总数也不能超过12,不能再声明数组、使用malloc
  • 函数不能递归
  • 矩阵数组A不能变动,B可以随意改变。

3.评定标准
共需要优化3种大小的数组,分别为32×32 64×64 61×67,评定标准为
得分

分析

 这里使用的Cache是(s=5,E=1,b=5),32组×32Bytes;其一组可以存8个int变量,这意味着“每8行矩阵,其对应的Cache就会开始循环”。
在这里插入图片描述
装完A[7]行时已经把Cache已满。
 显然整个Cache都装不下矩阵A或B,如果不做处理的话,那就是这种默认的情况。misses数达到了1183个。在这里插入图片描述在这里插入图片描述
 在默认情况下,miss过程:

  1. 按行读A[0][0],miss(cold miss),Cache加载A[0][0]~A[0][7]到Cache块0;
  2. 按列写B[0][0],miss(conflict miss),Cache加载B[0][0]~B[0][7]到Cache块0(冲突,替换掉A[0][0]~A[0][7]);
  3. 按行读A[0][1],miss(conflict miss,上次B替换掉了A的内容),Cache加载A[0][0]~A[0][7]到Cache块0;
  4. 按列写B[1][0],miss(cold miss),Cache加载B[1][0]~B[1][7]到Cache块4;
  5. 按行读A[0][2],hit,在“3.”中Cache已加载A[0][0]~A[0][7]到Cache块0;
  6. 按列写B[2][0],miss(cold miss),Cache加载B[2][0]~B[2][7]到Cache块8;
  7. 按行读A[0][8],miss(cold miss),Cache加载A[0][8]~A[0][15]到Cache块1;
  8. 按列写B[8][0],miss(conflict miss),Cache加载B[8][0]~B[8][7]到Cache块0(冲突,替换掉A[0][0]~A[0][7]);

从上述过程中可以知道,默认情况下miss主要由:

  1. 开始新一行的冷不命中,A每一行有4次;B每列每次都会不命中,一列有32次,共4×32+32×32=1152次miss在这里插入图片描述在这里插入图片描述
  2. 对角线上的元素反复冲突,像"1." "2."和"3."过程。共31次 (原因可看下面分析,这里有个映像即可)

所以共1152+31=1183次miss,与我们跑出来的结果一致
在这里插入图片描述

32×32
分析

 我们先来解决 默认情况下,矩阵B的Cache全部冲突的的问题。
造成该问题的主要原因“Cache最多能装矩阵B的8行,第9行开始循环使用Cache块号。造成B加载的行B[0]~ B[7]每行只用了一次B[0][0]、B[1][0]…就被后续读取的行B[8]~B[15]覆盖掉,存在很多浪费和miss。”
次要原因对角线上的元素反复冲突,像"1." "2."和"3."过程。 还有B[1][1] B[2][2] ...这些与A进行Cache的争夺,A需要加载两次,非对角线块却只需要加载一次。过程为:

  1. A[1][0],加载A[1][0]~A[1][7]
  2. B[0][1],加载B[0][0]~B[0][7]
  3. A[1][1],Cache已加载,命中了。
  4. B[1][1],加载B[1][0]~B[1][7],替换掉了"1."中A的Cache块
  5. A[1][2],再次加载A[1][0]~A[1][7]

核心点就是对角线上,A的上一次加载会被B替换,A需要再加载一次。
在这里插入图片描述

解决

解决主要原因
 使用Blocking分块技术,将整个矩阵分成小矩阵,使得Cache能够完全处理一个小矩阵,充分利用Cache。
  这里使用8×8的分块大小,因为
   1. Cache最多只能装矩阵8行,超过8行开始循环使用Cache块号。所以我们分块要将大小控制在8行及以内。
   2. Cache一组能装8个int值。
 两者结合,8×8分块呼之欲出。分完后的结构是:在这里插入图片描述
 A和B是一样的分块结构。此时矩阵B的Cache不会发生冲突。如A的黄色分块对应到B的黄色分块,A读黄色分块的1~ 29,对应对B写黄色分块的0~28块,不会使用相同的Cache块,故不会冲突。A的黄色方块没有读完不会读下一个蓝色方块,其对应的B黄色方块没有转置完A的黄色方块也不会读下一个方块蓝。
 这样就避免了“矩阵B[8]~ B[15]替换B[0]~ B[7]行,B[0]~ B[7]却只用了一次就丢弃” 造成的浪费和分块内部的冲突。
代码(建议先看一看,了解如何遍历,如何读取,建立起一个模型)
更改trans.c文件后,需要重新编译整个项目   make clean & make

  if(M==32 && N==32)
  {
    for(i=0;i<N;i+=8)
      for(j=0;j<M;j+=8)
        for(k=i;k<i+8;k++)
          for(m=j;m<j+8;m++)
            B[m][k]=A[k][m];
  }

8x8 when 32x32
 还不错,从1183->343,但还不够,需要miss<300才能满分,所以继续优化。
解决次要原因
 核心原因是对角线上,A的上一次加载会被B替换,A需要再加载一次。 那我们可以使A只读一次在缓冲中,B加载替换了也没关系,我们从缓冲中读A。
 看看题目要求,我们可以利用那12个int型变量,用其中8个变量存A分块的一行(8个)。这样就避免了二次加载A。
代码

  if(M==32 && N==32)
    for(i=0;i<N;i+=8)
      for(j=0;j<M;j+=8)
        for(k=i;k<i+8;k++)
        {
          v1=A[k][j+0];
          v2=A[k][j+1];
          v3=A[k][j+2];
          v4=A[k][j+3];
          v5=A[k][j+4];
          v6=A[k][j+5];
          v7=A[k][j+6];
          v8=A[k][j+7];
          B[j+0][k]=v1;
          B[j+1][k]=v2;
          B[j+2][k]=v3;
          B[j+3][k]=v4;
          B[j+4][k]=v5;
          B[j+5][k]=v6;
          B[j+6][k]=v7;
          B[j+7][k]=v8;
        }

reg when 32x32
此时已达到满分,但还能继续优化。

理论最小miss值优化

目标miss值

  1. 矩阵A的8×8分块的每一行产生一次miss,共32×4=128
  2. 矩阵B的8×8分块每一行产生一次miss,共32×4=128

共256次。但此时的优化结果为287,相差31,很显然只能是B的对角线还存在miss。
过程

  1. 读8×8分块A[0][0]~A[0][7],加载A[0][0]~A[0][7]
  2. 写8×8分块B[0][0]~B[7][0],加载B[0][0]~B[0][7] ... B[7][0]~[7][7]
  3. 读8×8分块A[1][0]~A[1][7],加载A[1][0]~A[1][7],替换掉B的部分
  4. 写8×8分块B[0][1]~B[7][1],只加载B[1][0]~B[1][7],其他的行上次已加载,替换掉"3."中A的部分

关键原因为对角线上,B的上一次加载会被A替换,B需要再加载一次。

在这里插入图片描述
在这里插入图片描述
解决:
两种思路:

  1. 在A访问第二行之前(即第一行),B不访问第二行
  2. 在A访问第二行之后,B不访问第二行

 第一种思路容易想到,之前我们是“读A 8×8分块的第一行,立刻转置到B 8×8分块的第一列”,此时B必然会访问到第二行。
 我们可以先将A的第二行读取了,存放在B中(因为A不可改动)第一行(因为B是按列访问,它的第一行暂时不会会使用),B在这个过程中只触发一次冷不命中,并且后续还能继续使用而不会造成miss。
 只有位于对角线上的8×8分块中会造成B的此类不命中,未在对角线上的8×8分块B是不会造成此类不命中的。
 所以我们只对对角线上的分块进行手动处理,其他的分块我们可以直接用B[m][k]=A[k][m]而不需要用第二次优化的临时变量了,因为A中的对角线元素命中同样会被手动处理解决。
代码

  if(M==32 && N==32)
  {
    for(i=0;i<M;i+=8)
      for(j=0;j<M;j+=8)
          if(i==j)
          {
            m=i;
            v1=A[m][j];v2=A[m][j+1];v3=A[m][j+2];v4=A[m][j+3];
            v5=A[m][j+4];v6=A[m][j+5];v7=A[m][j+6];v8=A[m][j+7];
  
            B[m][j]=v1;B[m][j+1]=v2;B[m][j+2]=v3;B[m][j+3]=v4;
            B[m][j+4]=v5;B[m][j+5]=v6;B[m][j+6]=v7;B[m][j+7]=v8;
  
            v1=A[m+1][j];v2=A[m+1][j+1];v3=A[m+1][j+2];v4=A[m+1][j+3];
            v5=A[m+1][j+4];v6=A[m+1][j+5];v7=A[m+1][j+6];v8=A[m+1][j+7];
  
            B[m+1][j]=B[m][j+1];B[m][j+1]=v1;
  
            B[m+1][j+1]=v2;B[m+1][j+2]=v3;B[m+1][j+3]=v4;
            B[m+1][j+4]=v5;B[m+1][j+5]=v6;B[m+1][j+6]=v7;B[m+1][j+7]=v8;
  
            v1=A[m+2][j];v2=A[m+2][j+1];v3=A[m+2][j+2];v4=A[m+2][j+3];
            v5=A[m+2][j+4];v6=A[m+2][j+5];v7=A[m+2][j+6];v8=A[m+2][j+7];
  
            B[m+2][j]=B[m][j+2];B[m+2][j+1]=B[m+1][j+2];
            B[m][j+2]=v1;B[m+1][j+2]=v2;B[m+2][j+2]=v3;
            B[m+2][j+3]=v4;B[m+2][j+4]=v5;B[m+2][j+5]=v6;B[m+2][j+6]=v7;B[m+2][j+7]=v8;
  
            v1=A[m+3][j];v2=A[m+3][j+1];v3=A[m+3][j+2];v4=A[m+3][j+3];
            v5=A[m+3][j+4];v6=A[m+3][j+5];v7=A[m+3][j+6];v8=A[m+3][j+7];
  
            B[m+3][j]=B[m][j+3];B[m+3][j+1]=B[m+1][j+3];B[m+3][j+2]=B[m+2][j+3];
            B[m][j+3]=v1;B[m+1][j+3]=v2;B[m+2][j+3]=v3;B[m+3][j+3]=v4;
            B[m+3][j+4]=v5;B[m+3][j+5]=v6;B[m+3][j+6]=v7;B[m+3][j+7]=v8;
  
            v1=A[m+4][j];v2=A[m+4][j+1];v3=A[m+4][j+2];v4=A[m+4][j+3];
            v5=A[m+4][j+4];v6=A[m+4][j+5];v7=A[m+4][j+6];v8=A[m+4][j+7];
  
            B[m+4][j]=B[m][j+4];B[m+4][j+1]=B[m+1][j+4];B[m+4][j+2]=B[m+2][j+4];B[m+4][j+3]=B[m+3][j+4];
            B[m][j+4]=v1;B[m+1][j+4]=v2;B[m+2][j+4]=v3;B[m+3][j+4]=v4;B[m+4][j+4]=v5;
            B[m+4][j+5]=v6;B[m+4][j+6]=v7;B[m+4][j+7]=v8;
  
            v1=A[m+5][j];v2=A[m+5][j+1];v3=A[m+5][j+2];v4=A[m+5][j+3];
            v5=A[m+5][j+4];v6=A[m+5][j+5];v7=A[m+5][j+6];v8=A[m+5][j+7];
  
            B[m+5][j]=B[m][j+5];B[m+5][j+1]=B[m+1][j+5];B[m+5][j+2]=B[m+2][j+5];B[m+5][j+3]=B[m+3][j+5];B[m+5][j+4]=B[m+4][j+5];
            B[m][j+5]=v1;B[m+1][j+5]=v2;B[m+2][j+5]=v3;B[m+3][j+5]=v4;B[m+4][j+5]=v5;B[m+5][j+5]=v6;
            B[m+5][j+6]=v7;B[m+5][j+7]=v8;
  
            v1=A[m+6][j];v2=A[m+6][j+1];v3=A[m+6][j+2];v4=A[m+6][j+3];
            v5=A[m+6][j+4];v6=A[m+6][j+5];v7=A[m+6][j+6];v8=A[m+6][j+7];
  
            B[m+6][j]=B[m][j+6];B[m+6][j+1]=B[m+1][j+6];B[m+6][j+2]=B[m+2][j+6];B[m+6][j+3]=B[m+3][j+6];
            B[m+6][j+4]=B[m+4][j+6];B[m+6][j+5]=B[m+5][j+6];
            B[m][j+6]=v1;B[m+1][j+6]=v2;B[m+2][j+6]=v3;B[m+3][j+6]=v4;B[m+4][j+6]=v5;B[m+5][j+6]=v6;
            B[m+6][j+6]=v7;B[m+6][j+7]=v8;
  
            v1=A[m+7][j];v2=A[m+7][j+1];v3=A[m+7][j+2];v4=A[m+7][j+3];
            v5=A[m+7][j+4];v6=A[m+7][j+5];v7=A[m+7][j+6];v8=A[m+7][j+7];
  
            B[m+7][j]=B[m][j+7];B[m+7][j+1]=B[m+1][j+7];B[m+7][j+2]=B[m+2][j+7];B[m+7][j+3]=B[m+3][j+7];
            B[m+7][j+4]=B[m+4][j+7];B[m+7][j+5]=B[m+5][j+7];B[m+7][j+6]=B[m+6][j+7];
            B[m][j+7]=v1;B[m+1][j+7]=v2;B[m+2][j+7]=v3;B[m+3][j+7]=v4;B[m+4][j+7]=v5;B[m+5][j+7]=v6;B[m+6][j+7]=v7;
            B[m+7][j+7]=v8;
            }
          else
          {
            for(k=i;k<i+8;k++)
              for(m=j;m<j+8;m++)
                B[m][k]=A[k][m];
          }

b diagonal optimize when 32x32
259次,比理论最小miss值多3次,这是函数调用造成的,可以用csim程序进行输出找到调用情况。此时已经是它的最优化状态了。

64×64
分析

 从32×32矩阵来看,主要解决了以下问题,实现了优化

  1. 分块大小,使得分块内部不产生冲突
  2. 对角线上,A的上一次加载会被B替换,A需要再加载一次。
  3. 对角线上,B的上一次加载会被A替换,B需要再加载一次。

64×64的优化也是这些内容,只是方式不同而已。
分块大小
Cache一行可以存8个int值,一共能存64×64矩阵的4行。可以这样分块

  1. 分块大小4×4。优点:64×64矩阵的第5行不会造成冲突,实现了分块内部不冲突。缺点:对于矩阵A而言,4×4分块时,读取一个分块中一行的一个元素,实际上也会加载下一个分块的一行,这样才能存满Cache一行,它可不知道你是用了4×4分块,这样对A没有影响,每行后四列下次可以继续使用。
    在这里插入图片描述
    但是,对于B而言,4×4分块会浪费每一行的后四列
    在这里插入图片描述
    在这里插入图片描述
 for(i=0;i<N;i+=4)
    for(j=0;j<M;j+=4)
      for(m=i;m<i+4;m++)
      {
        v1=A[m][j+0];
        v2=A[m][j+1];
        v3=A[m][j+2];
        v4=A[m][j+3];
        B[j+0][m]=v1;
        B[j+1][m]=v2;
        B[j+2][m]=v3;
        B[j+3][m]=v4;
      }

4x4 when 64x64

  1. 分块大小8×8。最大的问题是后4行会替换掉前4行,分块内部有冲突。
  for(i=0;i<N;i+=8)
    for(j=0;j<M;j+=8)
      for(m=i;m<i+8;m++)
      {
        v1=A[m][j+0];
        v2=A[m][j+1];
        v3=A[m][j+2];
        v4=A[m][j+3];
        v5=A[m][j+4];
        v6=A[m][j+5];
        v7=A[m][j+6];
        v8=A[m][j+7];
        B[j+0][m]=v1;
        B[j+1][m]=v2;
        B[j+2][m]=v3;
        B[j+3][m]=v4;
        B[j+4][m]=v5;
        B[j+5][m]=v6;
        B[j+6][m]=v7;
        B[j+7][m]=v8;
      }

8x8 when 64x64

  1. 分块大小8×8内分块成4个4×4.弥补“1.” “2.”中的的缺点,既不分块内部冲突,也充分利用Cache一行存8个int的特性。
    但我们需要对其进行处理,旨在尽可能用完Cache一块,且降低miss。
解决

我们使用分块大小8×8内分块成4个4×4的方式进行处理。

  1. 将A的左上和右上一次性复制给B
  2. 用本地变量把B的右上角存储下来
  3. 将A的左下复制给B的右上
  4. 利用上述存储B的右上角的本地变量,把A的右上复制给B的左下
  5. 把A的右下复制给B的右下
    在这里插入图片描述
  for(i=0;i<N;i+=8)
    for(j=0;j<M;j+=8)
    {
      //get A's first 4 rows 
      for(m=i;m<i+4;m++)
      {
        v1=A[m][j+0];
        v2=A[m][j+1];
        v3=A[m][j+2];
        v4=A[m][j+3];
        v5=A[m][j+4];
        v6=A[m][j+5];
        v7=A[m][j+6];
        v8=A[m][j+7];
        B[j+0][m]=v1;
        B[j+1][m]=v2;
        B[j+2][m]=v3;
        B[j+3][m]=v4;
        B[j+0][m+4]=v5;
        B[j+1][m+4]=v6;
        B[j+2][m+4]=v7;
        B[j+3][m+4]=v8;
      }
      //save B2 /get A3 
      for(m=j;m<j+4;m++)
      {
        //B2
        v1=B[m][i+4];
        v2=B[m][i+5];
        v3=B[m][i+6];
        v4=B[m][i+7];
        //A3.at this time,A3 A4 have all been load
        v5=A[i+4][m];
        v6=A[i+5][m];
        v7=A[i+6][m];
        v8=A[i+7][m];
        //A3 to B2
        B[m][i+4]=v5;
        B[m][i+5]=v6;
        B[m][i+6]=v7;
        B[m][i+7]=v8;
        //B2 to B3
        //now B3 B4 one line have been load
        B[m+4][i+0]=v1;
        B[m+4][i+1]=v2;
        B[m+4][i+2]=v3;
        B[m+4][i+3]=v4;
      }
      //A4 to B4
      for(m=i+4;m<i+8;m++)
      {
        v1=A[m][j+4];
        v2=A[m][j+5];
        v3=A[m][j+6];
        v4=A[m][j+7];
        B[j+4][m]=v1;
        B[j+5][m]=v2;
        B[j+6][m]=v3;
        B[j+7][m]=v4;
      }

4x4x4 when 64x64
已经拿到满分。

理论最小miss值优化

 和矩阵32×32一样,64×64存在理论最小值。64×64分成64个8×8分块,A B中每一个分块都是8个冷不命中,共64×8×2=1024次。
 如果要优化这里也要进行展开手动转置,展开代码太多且代码难以阅读(我实在卷不动了)。

但是,我还发现一篇使用“借用区块”而非展开达到1024理论值miss的文章《CSAPP - Cache Lab的更(最)优秀的解法》

61×67

 因为本题miss放得很宽松,<2000即可满分。使用16×16或者17×17分块即可通过。其实还有更细致的方法使得miss更少,但,卷不动了。

解决
for(i=0;i<N;i+=17)
      for(j=0;j<M;j+=17)
        for(m=i;m<N&&m<i+17;m++)
          for(n=j;n<M&&n<j+17;n++)
          {
            B[n][m]=A[m][n];
          }

实现文件

final points
Note:driver.py 是用python2版本的语法(print “”),用python3会出错,最好指明使用python2 运行
python
相关文件已上传到网盘。
在这里插入图片描述
PartA PartB C文件-微云
PartA PartB C文件-CSDN免积分

总结

《CS:App》配的Lab太好了,看完书过了1个月才来写CacheLab,发现啥思路也没有(也可能是习题没做)。果然看完书不代表就会了,必须要自己上机实践才能有更深的理解,与现实应用连接。

Reference

  1. 《CSAPP-Lab05 Cache Lab 深入解析》
  2. 《CSAPP实验之cache lab》
  3. 《深入理解计算机系统-cachelab》
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值