本 Lab 主要考察对计算机高速缓存(Cache)机制的理解,以及如何针对 Cache 进行程序的优化,对应知识点为书中的 6.4 ~ 6.6 节内容。
Part A: Writing a Cache Simulator
思路
基本流程
Part A 需要实现一个 Cache 模拟器,能够根据 valgrind 工具所生成的访存跟踪数据,模拟在特定参数的 Cache 环境下的命中(hits)次数、不命中(misses)次数和置换(evictions)次数,目标是实现与 csim-ref 同等的功能。模拟器需要具备的几个功能模块如下:
-
对命令行参数进行参数解析。
-
读取 trace 文件并解析为地址访问流。
-
定义 Cache 模拟器数据结构,以及相关的函数操作,包括初始化和地址访问。
-
遍历解析出来的地址访问流,依次进行访问模拟,计算得到命中次数等信息。
接下来分别对它们进行介绍。
命令行参数解析
根据实验手册的提示,可以使用 getopt
函数进行命令行参数的解析。另外,如果需要支持长选项(形如 --opt arg
),则可以使用 GNU C 库提供的扩展版本 getopt_long
函数,其使用方法如下:
- generated by GPT4o
函数原型
include <getopt.h> int getopt_long(int argc, char *const argv[], const char *optstring, const struct option *longopts, int *longindex);
参数说明
1.
argc
和argv
与标准
getopt
相同,分别表示命令行参数的个数和数组。2.
optstring
一个字符串,表示短选项的格式规则:
- 每个选项是一个字符。
- 如果选项需要参数,在字符后添加一个冒号(
:
)。- 如果选项的参数是可选的,在字符后添加两个冒号(
::
)。3.
longopts
一个指向
struct option
数组的指针,用于定义长选项。
struct option
定义如下:struct option { const char *name; // 长选项的名称 int has_arg; // 选项是否需要参数(no_argument, required_argument, optional_argument) int *flag; // 如果为 NULL,则返回值为 val;否则将 *flag 设置为 val 并返回 0 int val; // 短选项的字符值或自定义值 };
name
:长选项名称,例如"help"
对应--help
。has_arg
:
no_argument
(0):无参数。required_argument
(1):需要参数。optional_argument
(2):参数可选。flag
:
- 如果为
NULL
,getopt_long
会返回val
的值。- 如果非
NULL
,getopt_long
会将*flag
设置为val
,并返回 0。val
:指定与该长选项关联的返回值(通常与短选项的字符值一致)。4.
longindex
指向一个整型变量的指针,用于存储被解析的长选项在
longopts
数组中的索引位置。如果不需要,可以传NULL
。
返回值
- 返回短选项的字符值,或者由
struct option
中val
指定的值。- 遇到未知选项时返回
?
。- 当没有更多选项时,返回
-1
。
根据 getopt_long
的返回值,以及全局变量 optarg
,可以对不同的命令行参数进行分发处理。
// 参数定义
struct option long_options[] = {
{
"help", no_argument, NULL, 'h' },
{
"verbose", no_argument, NULL, 'v' },
{
"set", required_argument, NULL, 's' },
{
"lines", required_argument, NULL, 'E' },
{
"block", required_argument, NULL, 'b' },
{
"trace", required_argument, NULL, 't' },
{
0, 0, 0, 0 }
};
// 参数解析
int opt;
while ((opt = getopt_long(argc, argv, "hvs:E:b:t:", long_options, NULL)) != -1) {
switch (opt) {
case 'h':
print_usage();
return 0;
case 'v':
vflag = 1;
break;
case 's':
s = atoi(optarg);
break;
case 'E':
E = atoi(optarg);
break;
case 'b':
b = atoi(optarg);
break;
case 't':
trace_file = optarg;
break;
default:
print_usage();
return 1;
}
}
trace 文件解析
对 trace 文件进行解析,首先读取文件中的每一行操作,对于 operation 为 I 的访存操作,直接跳过不做处理。由于剩余的 M, L, S 操作都满足格式 [space]operation address,size
,因此可以直接使用 sscanf
进行解析,提取出各字段。
对不同操作的处理比较简单:L 和 S 操作需要一次访存,M 操作需要两次访存。
这里我没有考虑一次访存位于多个 Cache 行中的情况,始终视作一次/两次访存,也通过了全部测试样例。
char line[256];
while (fgets(line, sizeof(line), file)) {
if (line[0] == '\n' || line[0] != ' ') continue;
line[strlen(line) - 1] = '\0'; // 去除尾置换行符
char *pline = line + 1; // 去除前导空格
char operation;
int address, size;
if (sscanf(pline, " %c %x,%d", &operation, &address, &size) != 3) {
fprintf(stderr, "Invalid line format: %s\n", pline);
continue;
}
access_cache(&cache, address);
if (operation == 'M') {
access_cache(&cache, address);
}
}
Cache 模拟器数据结构设计
整个 Cache 模拟器包含 Cache 数据部分、Cache 的参数和命中次数等模拟结果。
typedef struct {
int s;
int E;
int b;
int set_num;
CacheSet *sets;
int hit_count;
int miss_count