实验手册:包含了实验指导和评分规则
实验ppt对上课内容的回顾和对实验细节的讲解,很值得一看

Part A

这一部分是要实现一个cache模拟器,需要记录命中次数,不命中次数和替换次数,通过这个可以让我们更加了解cache的运行方式,同时也可以更加了解LRU的替换策略。
这一部分需要了解的两个函数getopt() 和 fscanf() 在开头的ppt中有讲解。

先上代码

  1. #include "cachelab.h"
  2. #include <unistd.h>
  3. #include <getopt.h>
  4. #include <stdlib.h>
  5. #include <stdio.h>
  6. #include <string.h>
  7. int h, v, s, S, E, b, B; //定义全局变量方便调用
  8. char t[200];
  9. int hits, misses, evictions;
  10. long stempnow = 0;
  11. typedef struct
  12. {
  13. int valid; //有效位
  14. int tag; //标记位
  15. long LRU_stemp; //LRU替换的标记
  16. } cacheline, *cache_set, **_cache;
  17. _cache cache;
  18. //这一部分是为了实现-v下的回显,最终的测试并不要求这个
  19. typedef int result_tag;
  20. #define MISS 0
  21. #define HIT 1
  22. #define EVICTION 2
  23. void printUsage(); //-h打印使用方法
  24. void init_cache(); //初始化cache
  25. void close_cache(); //free掉cache
  26. void printverbose(char oper, unsigned int address, int size, char *opresult); //-v下打印回显
  27. void do_trace(); //解析trace并以此执行
  28. result_tag access_cache(char oper, unsigned int address, int size); //访问cache
  29. int main(int argc, char **argv)
  30. {
  31. int opt;
  32. while (-1 != (opt = (getopt(argc, argv, "hvs:E:b:t:"))))
  33. {
  34. switch (opt)
  35. {
  36. case 'h':
  37. h = 1;
  38. printUsage();
  39. break;
  40. case 'v':
  41. v = 1;
  42. break;
  43. case 's':
  44. s = atoi(optarg);
  45. break;
  46. case 'E':
  47. E = atoi(optarg);
  48. break;
  49. case 'b':
  50. b = atoi(optarg);
  51. break;
  52. case 't':
  53. strcpy(t, optarg);
  54. break;
  55. default:
  56. printUsage();
  57. break;
  58. }
  59. }
  60. if (s <= 0 || E <= 0 || b <= 0 || t == NULL) // 如果选项参数不合格就退出
  61. return -1;
  62. S = 1 << s;
  63. B = 1 << b;
  64. init_cache();
  65. do_trace();
  66. close_cache();
  67. printSummary(hits, misses, evictions);
  68. return 0;
  69. }
  70. void printUsage()
  71. {
  72. printf("Usage: ./csim-ref [-hv] -s <num> -E <num> -b <num> -t <file>
  73. "
  74. "Options:
  75. "
  76. " -h Print this help message.
  77. "
  78. " -v Optional verbose flag.
  79. "
  80. " -s <num> Number of set index bits.
  81. "
  82. " -E <num> Number of lines per set.
  83. "
  84. " -b <num> Number of block offset bits.
  85. "
  86. " -t <file> Trace file.
  87. "
  88. "Examples:
  89. "
  90. " linux> ./csim-ref -s 4 -E 1 -b 4 -t traces/yi.trace
  91. "
  92. " linux> ./csim-ref -v -s 8 -E 2 -b 4 -t traces/yi.trace
  93. ");
  94. }
  95. void init_cache()
  96. {
  97. cache = (_cache)malloc(sizeof(cache_set) * S);
  98. int i, j;
  99. for (i = 0; i < S; i++)
  100. {
  101. cache[i] = (cache_set)malloc(sizeof(cacheline) * E);
  102. for (j = 0; j < E; j++)
  103. {
  104. cache[i][j].tag = 0;
  105. cache[i][j].valid = 0;
  106. cache[i][j].LRU_stemp = 0;
  107. }
  108. }
  109. }
  110. void close_cache()
  111. {
  112. int i;
  113. for (i = 0; i < S; i++)
  114. free(cache[i]);
  115. free(cache);
  116. }
  117. void printverbose(char oper, unsigned int address, int size, char *opresult)
  118. {
  119. printf("%c %x,%d %s
  120. ", oper, address, size, opresult);
  121. }
  122. void do_trace()
  123. {
  124. FILE *fp = fopen(t, "r"); // 读取文件
  125. if (fp == NULL)
  126. {
  127. printf("open error");
  128. exit(-1);
  129. }
  130. char operation; // 命令开头的 I L M S
  131. unsigned int address; // 地址参数
  132. int size; // 大小
  133. while (fscanf(fp, " %c %xu,%d
  134. ", &operation, &address, &size) > 0)
  135. {
  136. size = 1;
  137. result_tag r;
  138. switch (operation)
  139. {
  140. case 'I':
  141. break;
  142. case 'L':
  143. r = access_cache(operation, address, size);
  144. if (v)
  145. {
  146. if (r == HIT)
  147. printverbose(operation, address, size, "hit");
  148. else if (r == MISS)
  149. printverbose(operation, address, size, "miss");
  150. else if (r == EVICTION)
  151. printverbose(operation, address, size, "miss eviction");
  152. }
  153. break;
  154. case 'S':
  155. r = access_cache(operation, address, size);
  156. if (v)
  157. {
  158. if (r == HIT)
  159. printverbose(operation, address, size, "hit");
  160. else if (r == MISS)
  161. printverbose(operation, address, size, "miss");
  162. else if (r == EVICTION)
  163. printverbose(operation, address, size, "miss eviction");
  164. }
  165. break;
  166. case 'M':
  167. r = access_cache(operation, address, size);
  168. hits++; //M的第二次一定命中
  169. if (v)
  170. {
  171. if (r == HIT)
  172. printverbose(operation, address, size, "hit hit");
  173. else if (r == MISS)
  174. printverbose(operation, address, size, "miss hit");
  175. else if (r == EVICTION)
  176. printverbose(operation, address, size, "miss eviction hit");
  177. }
  178. break;
  179. }
  180. }
  181. fclose(fp);
  182. }
  183. result_tag access_cache(char oper, unsigned int address, int size)
  184. {
  185. int access_tag, access_set;
  186. int tag_mask, set_mask;
  187. set_mask = ((1 << (b + s)) - 1) - ((1 << b) - 1); //组号掩码
  188. access_set = ((set_mask & address) >> b) % S; //组号
  189. tag_mask = -1 << (b + s); //tag的掩码
  190. access_tag = tag_mask & address; //tag值
  191. int i;
  192. for (i = 0; i < E; i++)
  193. {
  194. if (cache[access_set][i].valid && cache[access_set][i].tag == access_tag) //命中
  195. {
  196. hits++;
  197. cache[access_set][i].LRU_stemp = stempnow++;
  198. return HIT;
  199. }
  200. }
  201. for (i = 0; i < E; i++)
  202. {
  203. if (!cache[access_set][i].valid) //未命中找到空行
  204. {
  205. cache[access_set][i].valid = 1;
  206. cache[access_set][i].tag = access_tag;
  207. cache[access_set][i].LRU_stemp = stempnow++;
  208. misses++;
  209. return MISS;
  210. }
  211. }
  212. //未命中且需要进行行替换
  213. //寻找时间戳最小(最少最近使用)的行
  214. int eviction_line = 0;
  215. long maxstemp = ~((long)1 << 63);
  216. for (i = 0; i < E; i++)
  217. {
  218. if (cache[access_set][i].LRU_stemp < maxstemp)
  219. {
  220. eviction_line = i;
  221. maxstemp = cache[access_set][i].LRU_stemp;
  222. }
  223. }
  224. //行替换
  225. cache[access_set][eviction_line].valid = 1;
  226. cache[access_set][eviction_line].tag = access_tag;
  227. cache[access_set][eviction_line].LRU_stemp = stempnow++;
  228. misses++;
  229. evictions++;
  230. return EVICTION;
  231. }

为了实现LRU的替换策略,我的方法是维持一个stempnow的自建时间戳,每次对行有更新时就讲这个时间戳记录进去,同时时间戳自加1,这样替换时就只需要寻找时间戳最小的那一行替换就行。
这一部分总体上不难,主要困难在于理解cache的工作方式和设计如何去提取出组号与tag值。

Part B

这一部分需要我们实现矩阵的转置,要求尽可能地利用cache,即减少miss,总共有三道题。

32*32

我们首先看一下示例的蛮力法
CSAPP: cachelab - 图1
miss高达1183,远大于300的目标。
我们知道的是cache 大小为 s=5,E=1,b=5,即32组_1行_32字节,共256个int。
所以我们需要对矩阵进行分块,我们在转置的过程中尽量让块小于cache大小,因为cache可以装256个int,所以块大小定为8*8。
(这一步的代码没保存,就不贴了)
结果是
CSAPP: cachelab - 图2
仍没达到要求。
下一步我选择每次把每一行的8个数一次性读取,然后再一次性存到矩阵B中,这样可以进一步减少cache的替换,从而减少miss。

  1. if (M == 32)
  2. {
  3. int i, j, block_i;
  4. int t0, t1, t2, t3, t4, t5, t6, t7;
  5. for (i = 0; i < N; i += 8)
  6. {
  7. for (j = 0; j < M; j += 8)
  8. {
  9. for (block_i = 0; block_i < 8; block_i++)
  10. {
  11. t0 = A[i + block_i][j + 0];
  12. t1 = A[i + block_i][j + 1];
  13. t2 = A[i + block_i][j + 2];
  14. t3 = A[i + block_i][j + 3];
  15. t4 = A[i + block_i][j + 4];
  16. t5 = A[i + block_i][j + 5];
  17. t6 = A[i + block_i][j + 6];
  18. t7 = A[i + block_i][j + 7];
  19. B[j + 0][i + block_i] = t0;
  20. B[j + 1][i + block_i] = t1;
  21. B[j + 2][i + block_i] = t2;
  22. B[j + 3][i + block_i] = t3;
  23. B[j + 4][i + block_i] = t4;
  24. B[j + 5][i + block_i] = t5;
  25. B[j + 6][i + block_i] = t6;
  26. B[j + 7][i + block_i] = t7;
  27. }
  28. }
  29. }
  30. }

结果是
CSAPP: cachelab - 图3
终于通过了

64*64

这是最难的一部分,因为整个cache只能存储数组的四行,所以如果按照8_8分块的话仍然有很高的冲突,然而这一题要求有十分严苛,只能1300,所以需要更精细的方法,先按照8_8分块,在块内在按照44划分,同时不追求一次到位,而要求最高的cache重复访问,详细解析可以参考这篇博客
*代码:

else if (N == 64)
    {
        for (int i = 0; i < N; i += 8)
        {
            for (int j = 0; j < M; j += 8)
            {
                for (int k = i; k < i + 4; ++k)
                {
                    /* 读取1 2,暂时放在左下角1 2 */
                    int temp_value0 = A[k][j];
                    int temp_value1 = A[k][j + 1];
                    int temp_value2 = A[k][j + 2];
                    int temp_value3 = A[k][j + 3];
                    int temp_value4 = A[k][j + 4];
                    int temp_value5 = A[k][j + 5];
                    int temp_value6 = A[k][j + 6];
                    int temp_value7 = A[k][j + 7];

                    B[j][k] = temp_value0;
                    B[j + 1][k] = temp_value1;
                    B[j + 2][k] = temp_value2;
                    B[j + 3][k] = temp_value3;
                    /* 逆序放置 */
                    B[j][k + 4] = temp_value7;
                    B[j + 1][k + 4] = temp_value6;
                    B[j + 2][k + 4] = temp_value5;
                    B[j + 3][k + 4] = temp_value4;
                }
                for (int l = 0; l < 4; ++l)
                {
                    /* 按列读取 */
                    int temp_value0 = A[i + 4][j + 3 - l];
                    int temp_value1 = A[i + 5][j + 3 - l];
                    int temp_value2 = A[i + 6][j + 3 - l];
                    int temp_value3 = A[i + 7][j + 3 - l];
                    int temp_value4 = A[i + 4][j + 4 + l];
                    int temp_value5 = A[i + 5][j + 4 + l];
                    int temp_value6 = A[i + 6][j + 4 + l];
                    int temp_value7 = A[i + 7][j + 4 + l];

                    /* 从下向上按行转换2到3 */
                    B[j + 4 + l][i] = B[j + 3 - l][i + 4];
                    B[j + 4 + l][i + 1] = B[j + 3 - l][i + 5];
                    B[j + 4 + l][i + 2] = B[j + 3 - l][i + 6];
                    B[j + 4 + l][i + 3] = B[j + 3 - l][i + 7];
                    /* 将3 4放到正确的位置 */
                    B[j + 3 - l][i + 4] = temp_value0;
                    B[j + 3 - l][i + 5] = temp_value1;
                    B[j + 3 - l][i + 6] = temp_value2;
                    B[j + 3 - l][i + 7] = temp_value3;
                    B[j + 4 + l][i + 4] = temp_value4;
                    B[j + 4 + l][i + 5] = temp_value5;
                    B[j + 4 + l][i + 6] = temp_value6;
                    B[j + 4 + l][i + 7] = temp_value7;
                }
            }
        }
    }

结果
CSAPP: cachelab - 图4
像这样进行专门的优化,确实可以极大地提高cache的利用率,从而提高程序的效率,但是其缺点也很明显,只能针对专门的矩阵进行优化,不具有普适性,就像我们即将看到的第三题。

61*67

这一题由于矩阵没有什么特别的规律,所以不清楚在cache的映射情况,因此我们只能一步步地尝试分块的大小,最终发现大约在14*14时可以满足题目要求。

else
    { //没有什么特征,只能试块大小,在14的时候misses为1996,通过了
        int block_size = 14;
        int i, j, bl_i, bl_j;
        for (i = 0; i < N; i += block_size)
            for (j = 0; j < M; j += block_size)
                for (bl_i = 0; bl_i < block_size && bl_i + i < N; bl_i++)
                    for (bl_j = 0; bl_j < block_size && bl_j + j < M; bl_j++)
                        B[j + bl_j][i + bl_i] = A[i + bl_i][j + bl_j];
    }

满分合影

CSAPP: cachelab - 图5

完整代码