[关闭]
@SovietPower 2021-05-23T01:12:52.000000Z 字数 6745 阅读 1380

CSAPP Lab4

CSAPP



https://www.zybuluo.com/SovietPower/note/1795924
参考:
https://www.jianshu.com/p/e68dd8305e9c

得分测试:

  1. Linux> make
  2. Linux> python driver.py

得分截图:

Part 1

要求:模拟一个组相连高速缓存。只需给出每次操作是否命中、是否发生驱逐即可。

组相连高速缓存有组,每组行,每行个有效位,个标记位,数据位。
地址:个标记位,个组索引位,个块偏移位。
每次模拟即可。

代码还可以优化的地方(但也没必要):s,b,t,verbose,cache等确实可以定义为局部变量并作为函数参数传递;Cache结构体应有自己的s,b,t,而不是全局的。

测试得分:

  1. Linux> make
  2. linux> ./test-csim

自测:

  1. > csim(.exe) -v -s 4 -E 1 -b 4 -t traces/yi.trace
  2. L 10,1 miss
  3. M 20,1 miss hit
  4. ...
  5. M 12,1 miss eviction hit
  6. hits:4 misses:5 evictions:3

代码:

  1. #include "cachelab.h"
  2. #include <stdio.h>
  3. #include <getopt.h>
  4. #include <stdlib.h>
  5. #include <unistd.h>
  6. typedef unsigned long long ull;
  7. const char *Usage="Usage: ./csim-ref [-hv] -s <s> -E <E> -b <b> -t <tracefile>";
  8. const char *Error="Wrong augment.";
  9. typedef struct
  10. {
  11. int valid, tm;
  12. ull tag;
  13. }CacheLine; //缓存行
  14. typedef CacheLine* CacheSet; //缓存组
  15. typedef CacheSet* Cache; //组相连高速缓存
  16. int s,S,E,b,B;
  17. int verbose;
  18. int hits=0, misses=0, evictions=0;
  19. Cache cache;
  20. FILE *fp=NULL; //<tracefile>
  21. void Read(int argc, char* argv[])
  22. {
  23. for(int opt; ~(opt=getopt(argc, argv, "hvs:E:b:t:")); )
  24. {
  25. switch(opt)
  26. {
  27. case 'h': //Optional help flag that prints usage info
  28. fprintf(stdout, Usage, argv[0]);
  29. exit(0);
  30. case 'v': //Optional verbose flag that displays trace info
  31. verbose=1;
  32. break;
  33. case 's': //Number of set index bits (S = 2^s is the number of sets)
  34. s=atoi(optarg), S=1<<s;
  35. break;
  36. case 'E': //Associativity (number of lines per set)
  37. E=atoi(optarg);
  38. break;
  39. case 'b': //Number of block bits (B = 2^b is the block size)
  40. b=atoi(optarg), B=1<<b;
  41. break;
  42. case 't': //-t <tracefile>: Name of the valgrind trace to replay
  43. fp=fopen(optarg,"r");
  44. break;
  45. default:
  46. fprintf(stdout, Error, argv[0]);
  47. exit(1);
  48. }
  49. }
  50. }
  51. void Init()
  52. {
  53. cache=(Cache)malloc(sizeof(CacheSet)*S);
  54. if(cache==NULL) exit(1);
  55. for(int i=0; i<S; ++i)
  56. {
  57. cache[i]=(CacheSet)calloc(E,sizeof(CacheLine)); //calloc会初始化分配的内容为0,malloc不会
  58. if(cache[i]==NULL) exit(1);
  59. }
  60. }
  61. void Load(int set_id,ull tag)
  62. {
  63. static int TIME=0;
  64. ++TIME; //time stamp
  65. int empty=-1, eviction=-1;
  66. CacheSet set=cache[set_id];
  67. for(int i=0; i<E; ++i)
  68. {
  69. if(set[i].valid)
  70. {
  71. if(set[i].tag==tag)
  72. {
  73. ++hits, set[i].tm=TIME;
  74. if(verbose) printf(" hit");
  75. return;
  76. }
  77. if(eviction==-1 || set[i].tm<set[eviction].tm)
  78. eviction=i;
  79. }
  80. else empty=i;
  81. }
  82. ++misses;
  83. if(~empty)
  84. {
  85. set[empty].valid=1, set[empty].tag=tag;
  86. set[empty].tm=TIME;
  87. if(verbose) printf(" miss");
  88. }
  89. else
  90. {
  91. ++evictions;
  92. set[eviction].tag=tag;
  93. set[eviction].tm=TIME;
  94. if(verbose) printf(" miss eviction");
  95. }
  96. }
  97. void Store(int set_id,ull tag)
  98. {
  99. Load(set_id,tag);
  100. }
  101. void Simulater()
  102. {
  103. char opt;
  104. ull add;
  105. int size;
  106. while(~fscanf(fp," %c %llx,%d",&opt,&add,&size)) //%I64x
  107. {
  108. if(opt=='I') continue;
  109. int set_id=(add>>b)&(S-1); ull tag=add>>(s+b);
  110. if(verbose) printf("%c %llx,%d",opt,add,size);
  111. switch(opt)
  112. {
  113. case 'L':
  114. Load(set_id, tag);
  115. break;
  116. case 'S':
  117. Store(set_id, tag);
  118. break;
  119. case 'M':
  120. Load(set_id, tag), Store(set_id, tag);
  121. break;
  122. }
  123. if(verbose) putchar('\n');
  124. }
  125. }
  126. // void printSummary(int a,int b,int c)
  127. // {
  128. // printf("hits:%d misses:%d evictions:%d\n",a,b,c);
  129. // }
  130. int main(int argc, char* argv[])
  131. {
  132. Read(argc,argv);
  133. Init();
  134. Simulater();
  135. printSummary(hits,misses,evictions);
  136. fclose(fp);
  137. for(int i=0; i<S; ++i) free(cache[i]);
  138. free(cache); //Always free what you malloc, otherwise may get memory leak
  139. return 0;
  140. }

Part 2

要求:给定一个的直接映射缓存,即有组,每组一行能存字节/个int。分别对的矩阵转置函数做修改,使得其miss数尽量小。
三个函数允许的最大miss数分别为:,满分为小于
此外表现分要求:
1. 每次最多使用12个局部int变量。
2. 不能定义数组。
3. 不能改变A矩阵,但B矩阵可以任意修改。

trans.c中修改。
若要测试例如的矩阵转置可以使用:
Linux> ./test-trans -M 12 -N 34

自测:

  1. Linux> make
  2. Linux> ./test-trans -M <M> -N <N>

查看错误信息:

  1. Linux> ./tracegen -M 64 -N 64 -F 0

2.1 32×32

做法1

先看一下分组情况:

第一行:
第二行:
第三行:
...
第八行:

转置时,A是读同一行,所以miss会很小;而对B需要逐列操作,miss的多少主要取决于B,所以主要关注B。
一般的转置中,B依次写行,依次占用第组。可以发现如果只让B写八行的第一列,再写这八行的下一列直至写完前八列,就不会发生miss/eviction(当然第一列会miss)。
至于A的影响,只是在分块矩阵的主对角线上时,B每写一列(八行)会与A冲突一次,导致两次miss。(注意要先存一下A这行的八个元素,才能写B的这一列,否则A要多miss一次)

结果分析:
对于主对角线上的四个矩阵,miss数为次;非主对角线上的子矩阵,因为A,B用到的内存组错开了,所以只有次miss。
所以总miss数约为,实测

做法2

考虑能否消除做法1中主对角线上,额外的次miss。这次miss,是因为A,B是 分别按行列 交叉读写的,所以总会有次的覆盖。
那么我们A,B全都按行读写,就不会有这次覆盖了,每次的一整行都能hit。
注意到缓存可以存下A或B的整个矩阵,不能修改A,但可以修改B。
所以我们把A一行一行赋值给B,是次miss;然后再对B的矩阵做转置,额外miss数就是0了(全部在缓存中,全部都hit)。
先按行复制后整个转置

这样总miss数为,实测

代码:

  1. //Sol1: 287
  2. // for(i=0; i<N; i+=8)
  3. // for(j=0; j<M; j+=8)
  4. // for(k=i; k<i+8; ++k)
  5. // {
  6. // v0=A[k][j], v1=A[k][j+1], v2=A[k][j+2], v3=A[k][j+3],
  7. // v4=A[k][j+4], v5=A[k][j+5], v6=A[k][j+6], v7=A[k][j+7];
  8. //
  9. // B[j][k]=v0, B[j+1][k]=v1, B[j+2][k]=v2, B[j+3][k]=v3,
  10. // B[j+4][k]=v4, B[j+5][k]=v5, B[j+6][k]=v6, B[j+7][k]=v7;
  11. // }
  12. // return;
  13. //Sol2: 259
  14. for(i=0; i<N; i+=8)
  15. for(j=0; j<M; j+=8)
  16. {
  17. for(k=i, l=j; k<i+8; ++k, ++l)
  18. {
  19. v0=A[k][j], v1=A[k][j+1], v2=A[k][j+2], v3=A[k][j+3],
  20. v4=A[k][j+4], v5=A[k][j+5], v6=A[k][j+6], v7=A[k][j+7];
  21. B[l][i]=v0, B[l][i+1]=v1, B[l][i+2]=v2, B[l][i+3]=v3,
  22. B[l][i+4]=v4, B[l][i+5]=v5, B[l][i+6]=v6, B[l][i+7]=v7;
  23. }
  24. for(k=0; k<8; ++k)//第j行 第i列(偏移量i,j记得加)
  25. for(l=k+1; l<8; ++l)//对于B,行的编号就都加j,列的编号就都加i
  26. v0=B[k+j][l+i], B[k+j][l+i]=B[l+j][k+i], B[l+j][k+i]=v0;
  27. }

2.2 64×64

看一下分组情况:

第一行:
第二行:
第三行:
第四行:
第五行:组...

显然还按分组不利于B的按列写。如果换成分组,B的每列写不会冲突,但缓存中每行的利用率只有一半(能得一点点分)。

考虑综合两种分法,整体采用分组,按行的操作每次处理八个,按列的操作每次处理四个(但注意这四行中的前八列都可以用)。

参考上面链接中的做法及图片:
对每个矩阵,分成四个矩阵,然后做四步:

1. 对A的前四行,前四个数分别存到B的前四列中,后四个数分别存到B的后四列中。
2. 读A的后四行的第一列、第五列元素,并保存。

3. 将2.中A的后四行的第一列,放到B当前第一行的后四个元素中(并保存之前这里的四个元素);将2.中A的后四行的第五列,放到B第五行的后四列去。
4. 将3.中存下的,原先B第一行的后四个元素,放到B第五行的前四列去。
5. 步对不同列重复做共四次,就可以实现转置了。

这个做法充分利用了,同一行能读个数、最多同时存整行的性质,以及转置的特点。
总结就是,前四行可以直接按列赋值给B,然后对后四行,左下角部分的每一列放到右上角部分的每一行、右上角的每一行放到左下角部分的每一行、右下角的列转成行位置,就可以实现转置,而且这种方法对内存的利用率很高、miss少。

结果分析:
对于上面的四步,若子矩阵不在主对角线上,则A,B的读写不会相互影响,则:
1. miss数为
2. miss数为
3. miss数为
4. miss数为
5. 三次重复后的步miss数共

总miss数约为

若子矩阵在主对角线上,1.中miss数会加,每次的3.会多(写B的被A驱逐的前四行)+(读A的被B驱逐的五六七行)=次,所以共多次。

所以总miss数为次,实测次。
1.中多的次miss可以像2.1中,先复制后转置的方法消除。

代码:

  1. for(i=0; i<N; i+=8)
  2. for(j=0; j<M; j+=8)
  3. {
  4. for(k=i; k<i+4; ++k)
  5. {
  6. v0=A[k][j+0], v1=A[k][j+1], v2=A[k][j+2], v3=A[k][j+3],
  7. v4=A[k][j+4], v5=A[k][j+5], v6=A[k][j+6], v7=A[k][j+7];
  8. B[j][k]=v0, B[j+1][k]=v1, B[j+2][k]=v2, B[j+3][k]=v3,
  9. B[j][k+4]=v4, B[j+1][k+4]=v5, B[j+2][k+4]=v6, B[j+3][k+4]=v7;
  10. }
  11. for(k=j; k<j+4; ++k)
  12. {
  13. v0=A[i+4][k], v4=A[i+4][k+4],
  14. v1=A[i+5][k], v5=A[i+5][k+4],
  15. v2=A[i+6][k], v6=A[i+6][k+4],
  16. v3=A[i+7][k], v7=A[i+7][k+4],
  17. tmp=B[k][i+4], B[k][i+4]=v0, v0=tmp;
  18. tmp=B[k][i+5], B[k][i+5]=v1, v1=tmp;
  19. tmp=B[k][i+6], B[k][i+6]=v2, v2=tmp;
  20. tmp=B[k][i+7], B[k][i+7]=v3, v3=tmp;
  21. B[k+4][i+0]=v0, B[k+4][i+1]=v1, B[k+4][i+2]=v2, B[k+4][i+3]=v3,
  22. B[k+4][i+4]=v4, B[k+4][i+5]=v5, B[k+4][i+6]=v6, B[k+4][i+7]=v7;
  23. }
  24. }

2.3 67×61

类似2.1,每行是个,所以依旧利用Cache能存B的行的性质,对B每行处理一次,每次处理所有行的这八个元素的A->B赋值。
因为限制为足够大,比较容易过。实测

代码:

  1. for(j=0; j<56; j+=8)
  2. for(i=0; i<67; ++i)
  3. {
  4. v0=A[i][j], v1=A[i][j+1], v2=A[i][j+2], v3=A[i][j+3],
  5. v4=A[i][j+4], v5=A[i][j+5], v6=A[i][j+6], v7=A[i][j+7];
  6. B[j][i]=v0, B[j+1][i]=v1, B[j+2][i]=v2, B[j+3][i]=v3,
  7. B[j+4][i]=v4, B[j+5][i]=v5, B[j+6][i]=v6, B[j+7][i]=v7;
  8. }
  9. for(i=0; i<64; ++i)//右上
  10. for(j=56; j<61; ++j)
  11. B[j][i]=A[i][j];
  12. for(i=64; i<67; ++i)//右下
  13. for(j=56; j<61; ++j)
  14. B[j][i]=A[i][j];
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注