如何优化基于FIFO的二级包含式缓存模拟器read_fifo函数?
两级FIFO缓存模拟器优化实现
原实现的核心问题
- 未正确利用
timeStamp字段实现FIFO逻辑,替换规则错误(简单替换第一个块或通过tag判断替换对象,不符合先进先出) - 违反包含性缓存原则:将L1的块替换到L2,导致L1数据不再是L2的子集,违背设计要求
- 缺少有效位(Valid Bit):初始状态下缓存块tag可能为0,会出现误命中
- 代码冗余:L1/L2查找逻辑重复,存在不必要的内存拷贝操作
优化后的完整实现代码
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <stdint.h> // 全局参数定义 #define DRAM_SIZE 1048576 #define BLOCK_SIZE 16 // 单缓存块大小:16字节 #define BLOCK_OFFSET_BITS 4 // 块偏移位宽:2^4=16 // L1缓存参数:64字节,2路组相连 #define L1_TOTAL_SIZE 64 #define L1_WAYS 2 #define L1_SETS (L1_TOTAL_SIZE / (L1_WAYS * BLOCK_SIZE)) // 组数:64/(2*16)=2 #define L1_SET_BITS 1 // 组索引位宽:2^1=2组 #define L1_TAG_BITS (32 - BLOCK_OFFSET_BITS - L1_SET_BITS) // L2缓存参数:256字节,4路组相连 #define L2_TOTAL_SIZE 256 #define L2_WAYS 4 #define L2_SETS (L2_TOTAL_SIZE / (L2_WAYS * BLOCK_SIZE)) // 组数:256/(4*16)=4 #define L2_SET_BITS 2 // 组索引位宽:2^2=4组 #define L2_TAG_BITS (32 - BLOCK_OFFSET_BITS - L2_SET_BITS) // 缓存块结构体:添加有效位,完善FIFO所需的时间戳 typedef struct cb_struct { unsigned char data[BLOCK_SIZE]; uint32_t tag; uint32_t timeStamp; // 记录块加载的周期数,用于FIFO替换 uint8_t valid; // 有效位:1=块有效,0=块无效 } cacheBlock; // 缓存访问请求结构体 typedef struct access { int readWrite; // 0=读操作,1=写操作 uint32_t address; uint32_t data; // 读操作时此值为0 } cacheAccess; // 全局资源 unsigned char *DRAM; cacheBlock L1_cache[L1_SETS][L1_WAYS]; cacheBlock L2_cache[L2_SETS][L2_WAYS]; FILE *trace; long cycles; // 全局周期计数器,每次访问递增 // 地址解析宏:避免硬编码,提升可读性 #define GET_BLOCK_OFFSET(addr) ((addr) & ((1 << BLOCK_OFFSET_BITS) - 1)) #define GET_L1_SET(addr) (((addr) >> BLOCK_OFFSET_BITS) & ((1 << L1_SET_BITS) - 1)) #define GET_L1_TAG(addr) ((addr) >> (BLOCK_OFFSET_BITS + L1_SET_BITS)) #define GET_L2_SET(addr) (((addr) >> BLOCK_OFFSET_BITS) & ((1 << L2_SET_BITS) - 1)) #define GET_L2_TAG(addr) ((addr) >> (BLOCK_OFFSET_BITS + L2_SET_BITS)) // 在指定缓存组中查找tag对应的块,找到返回索引,未找到返回-1 int cache_lookup(cacheBlock set[], int ways, uint32_t tag) { for (int i = 0; i < ways; i++) { if (set[i].valid && set[i].tag == tag) { return i; } } return -1; } // 找到组中需要替换的FIFO块(时间戳最小的最老块) int fifo_victim(cacheBlock set[], int ways) { int victim_idx = 0; uint32_t oldest_time = set[0].timeStamp; for (int i = 1; i < ways; i++) { if (set[i].timeStamp < oldest_time) { oldest_time = set[i].timeStamp; victim_idx = i; } } return victim_idx; } // 初始化DRAM:分配内存并填充测试数据 void init_DRAM() { DRAM = (unsigned char*)malloc(DRAM_SIZE); if (!DRAM) { perror("Failed to allocate DRAM"); exit(EXIT_FAILURE); } // 填充递增的测试数据,方便验证 for (int i = 0; i < DRAM_SIZE; i++) { DRAM[i] = i & 0xFF; } } // 初始化L1/L2缓存:重置有效位、tag、时间戳 void init_caches() { // 初始化L1 for (int s = 0; s < L1_SETS; s++) { for (int w = 0; w < L1_WAYS; w++) { L1_cache[s][w].valid = 0; L1_cache[s][w].tag = 0; L1_cache[s][w].timeStamp = 0; memset(L1_cache[s][w].data, 0, BLOCK_SIZE); } } // 初始化L2 for (int s = 0; s < L2_SETS; s++) { for (int w = 0; w < L2_WAYS; w++) { L2_cache[s][w].valid = 0; L2_cache[s][w].tag = 0; L2_cache[s][w].timeStamp = 0; memset(L2_cache[s][w].data, 0, BLOCK_SIZE); } } cycles = 0; } // 核心FIFO读操作函数,严格遵守包含性缓存规则 uint32_t read_fifo(uint32_t address) { cycles++; // 每次访问递增周期计数器 uint8_t offset = GET_BLOCK_OFFSET(address); uint32_t l1_set = GET_L1_SET(address); uint32_t l1_tag = GET_L1_TAG(address); uint32_t l2_set = GET_L2_SET(address); uint32_t l2_tag = GET_L2_TAG(address); // 第一步:查找L1缓存 int l1_idx = cache_lookup(L1_cache[l1_set], L1_WAYS, l1_tag); if (l1_idx != -1) { // L1命中,直接返回对应字节 return L1_cache[l1_set][l1_idx].data[offset]; } // 第二步:查找L2缓存 int l2_idx = cache_lookup(L2_cache[l2_set], L2_WAYS, l2_tag); if (l2_idx != -1) { // L2命中,将块加载到L1(L2块保留,遵守包含性) int l1_victim = fifo_victim(L1_cache[l1_set], L1_WAYS); // 更新L1受害者块 memcpy(L1_cache[l1_set][l1_victim].data, L2_cache[l2_set][l2_idx].data, BLOCK_SIZE); L1_cache[l1_set][l1_victim].tag = l1_tag; L1_cache[l1_set][l1_victim].valid = 1; L1_cache[l1_set][l1_victim].timeStamp = cycles; return L1_cache[l1_set][l1_victim].data[offset]; } // 第三步:DRAM命中,先加载到L2,再加载到L1 unsigned char *dram_block = DRAM + (address & ~((1 << BLOCK_OFFSET_BITS) - 1)); // 更新L2受害者块 int l2_victim = fifo_victim(L2_cache[l2_set], L2_WAYS); memcpy(L2_cache[l2_set][l2_victim].data, dram_block, BLOCK_SIZE); L2_cache[l2_set][l2_victim].tag = l2_tag; L2_cache[l2_set][l2_victim].valid = 1; L2_cache[l2_set][l2_victim].timeStamp = cycles; // 更新L1受害者块 int l1_victim = fifo_victim(L1_cache[l1_set], L1_WAYS); memcpy(L1_cache[l1_set][l1_victim].data, L2_cache[l2_set][l2_victim].data, BLOCK_SIZE); L1_cache[l1_set][l1_victim].tag = l1_tag; L1_cache[l1_set][l1_victim].valid = 1; L1_cache[l1_set][l1_victim].timeStamp = cycles; return L1_cache[l1_set][l1_victim].data[offset]; } // 打印L1缓存内容(符合要求的格式) void print_L1_cache() { printf("L1 Cache Contents:\n"); for (int s = 0; s < L1_SETS; s++) { printf("Set %d : ", s); for (int w = 0; w < L1_WAYS; w++) { if (L1_cache[s][w].valid) { printf("Tag=0x%08X | ", L1_cache[s][w].tag); } else { printf("Invalid | "); } } printf("\n"); } } // 打印L2缓存内容(符合要求的格式) void print_L2_cache() { printf("L2 Cache Contents:\n"); for (int s = 0; s < L2_SETS; s++) { printf("Set %d : ", s); for (int w = 0; w < L2_WAYS; w++) { if (L2_cache[s][w].valid) { printf("Tag=0x%08X | ", L2_cache[s][w].tag); } else { printf("Invalid | "); } } printf("\n"); } }
优化点详细解释
1. 参数结构化定义
用宏定义缓存的所有核心参数(大小、路数、组数、地址位宽),避免硬编码。修改缓存配置时只需修改对应宏,其他计算自动更新,大幅提升可维护性。
2. 完善缓存块结构体
添加valid有效位:解决初始状态下tag为0导致的误命中问题,只有有效位为1且tag匹配的块才被视为命中。
3. 正确实现FIFO替换
利用全局cycles计数器记录每个块的加载时间,替换时选择时间戳最小的最老块,严格遵循先进先出规则,替换逻辑清晰可验证。
4. 严格遵守包含性缓存规则
包含性要求L1中的所有数据必须同时存在于L2中:
- L1命中时,L2必然包含该块(加载L1时已确保L2存在对应块)
- L2命中加载到L1时,L2块保持不变,仅更新L1
- 从DRAM加载时,先更新L2再更新L1,确保L2包含所有L1数据
5. 代码复用与抽象
将缓存查找、FIFO受害者选择逻辑抽象为通用函数,避免重复代码,减少出错概率,同时提升代码可读性。
6. 修复冗余操作
移除原代码中重复的内存拷贝操作,仅在必要时执行一次拷贝,提升模拟效率。
内容的提问来源于stack exchange,提问作者Dave Shah
相关产品推荐
相关产品推荐

