由于cache的实现有代码框架,比较easy,就直接放代码了
c o d e code code
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "cachelab.h"
//#define DEBUG_ON
#define ADDRESS_LENGTH 64
/* Type: Memory address */
typedef unsigned long long int mem_addr_t;
/* Type: Cache line
LRU is a counter used to implement LRU replacement policy */
typedef struct cache_line {
char valid;
mem_addr_t tag;
unsigned long long int lru;
} cache_line_t;
typedef cache_line_t* cache_set_t;
typedef cache_set_t* cache_t;
/* Globals set by command line args */
int verbosity = 0; /* print trace if set */
int s = 0; /* set index bits */
int b = 0; /* block offset bits */
int E = 0; /* associativity */
char* trace_file = NULL;
/* Derived from command line args */
int S; /* number of sets */
int B; /* block size (bytes) */
/* Counters used to record cache statistics */
int miss_count = 0;
int hit_count = 0;
int eviction_count = 0;
unsigned long long int lru_counter = 1;
/* The cache we are simulating */
cache_t cache;
mem_addr_t set_index_mask;
/*
* initCache - Allocate memory, write 0's for valid and tag and LRU
* also computes the set_index_mask
*/
void initCache(){
int i,j;
cache=(cache_t)malloc(sizeof(cache_set_t)*S);
for(i=0;i<S;++i)
cache[i]=(cache_set_t)malloc(sizeof(cache_line_t)*E);
for(i=0;i<S;++i)
for(j=0;j<E;++j)
cache[i][j].valid='0',cache[i][j].tag=cache[i][j].lru=0;
mem_addr_t o=~(0x0);
set_index_mask=(o>>(ADDRESS_LENGTH-s-b)) & (o<<b);
}
/*
* freeCache - free allocated memory
*/
void freeCache(){
free(cache);
}
/*
* accessData - Access data at memory address addr.
* If it is already in cache, increast hit_count
* If it is not in cache, bring it in cache, increase miss count.
* Also increase eviction_count if a line is evicted.
*/
void accessData(mem_addr_t addr){
int i,o=-1,evi;
mem_addr_t now_b,now_t,minn;
minn=++lru_counter;
now_b=(addr&set_index_mask)>>b;
now_t=addr>>(s+b);
for(i=0;i<E;++i){
if(cache[now_b][i].valid=='1'&&cache[now_b][i].tag==now_t){
++hit_count;
cache[now_b][i].lru=lru_counter;
if(verbosity) printf("hit ");
return ;
}
else if(cache[now_b][i].valid=='0') o=i;
else{
if(cache[now_b][i].lru<minn){
minn=cache[now_b][i].lru;
evi=i;
}
}
}
++miss_count;
if(verbosity) printf("miss ");
if(o!=-1){
cache[now_b][o].valid='1';
cache[now_b][o].tag=now_t;
cache[now_b][o].lru=lru_counter;
return ;
}
++eviction_count;
cache[now_b][evi].tag=now_t;
cache[now_b][evi].lru=lru_counter;
if(verbosity) printf("eviction ");
}
/*
* replayTrace - replays the given trace file against the cache
*/
void replayTrace(char* trace_fn)
{
char buf[1000];
mem_addr_t addr=0;
unsigned int len=0;
FILE* trace_fp = fopen(trace_fn, "r");
if(!trace_fp){
fprintf(stderr, "%s: %s\n", trace_fn, strerror(errno));
exit(1);
}
while( fgets(buf, 1000, trace_fp) != NULL) {
if(buf[1]=='S' || buf[1]=='L' || buf[1]=='M') {
sscanf(buf+3, "%llx,%u", &addr, &len);
if(verbosity)
printf("%c %llx,%u ", buf[1], addr, len);
accessData(addr);
/* If the instruction is R/W then access again */
if(buf[1]=='M')
accessData(addr);
if (verbosity)
printf("\n");
}
}
fclose(trace_fp);
}
/*
* printUsage - Print usage info
*/
void printUsage(char* argv[])
{
printf("Usage: %s [-hv] -s -E -b -t \n" , argv[0]);
printf("Options:\n");
printf(" -h Print this help message.\n");
printf(" -v Optional verbose flag.\n");
printf(" -s Number of set index bits.\n" );
printf(" -E Number of lines per set.\n" );
printf(" -b Number of block offset bits.\n" );
printf(" -t Trace file.\n" );
printf("\nExamples:\n");
printf(" linux> %s -s 4 -E 1 -b 4 -t traces/yi.trace\n", argv[0]);
printf(" linux> %s -v -s 8 -E 2 -b 4 -t traces/yi.trace\n", argv[0]);
exit(0);
}
/*
* main - Main routine
*/
int main(int argc, char* argv[])
{
char c;
while( (c=getopt(argc,argv,"s:E:b:t:vh")) != -1){
switch(c){
case 's':
s = atoi(optarg);
break;
case 'E':
E = atoi(optarg);
break;
case 'b':
b = atoi(optarg);
break;
case 't':
trace_file = optarg;
break;
case 'v':
verbosity = 1;
break;
case 'h':
printUsage(argv);
exit(0);
default:
printUsage(argv);
exit(1);
}
}
/* Make sure that all required command line args were specified */
if (s == 0 || E == 0 || b == 0 || trace_file == NULL) {
printf("%s: Missing required command line argument\n", argv[0]);
printUsage(argv);
exit(1);
}
/* Compute S, E and B from command line args */
S=1<<s;
B=1<<b;
/* Initialize cache */
initCache();
#ifdef DEBUG_ON
printf("DEBUG: S:%u E:%u B:%u trace:%s\n", S, E, B, trace_file);
printf("DEBUG: set_index_mask: %llu\n", set_index_mask);
#endif
replayTrace(trace_file);
/* Free allocated memory */
freeCache();
/* Output the hit and miss statistics for the autograder */
printSummary(hit_count, miss_count, eviction_count);
return 0;
}
**
**
首先应当先了解一下A和B中的每一个元素对应到cache中的哪一个块中:
B跟A也相同。
从给出的cache大小可以看出,每一行可以存储32个字节,也就是8个int,所以每当处理一个数据时,就会缓存他后面的7个数据。 这样顺序赋值的时候已经可以保证A数组的命中率比较高了。但是这样有一个问题就是因为B的行每次会改变,所以B每次都是未命中的状态,不能达到要求。
这样我们将矩阵分成8X8的块,结合下面的图我们可以看一下。在给这个块内的B的第一列赋值的时候,每次还是未命中的状态。但是当给第二列赋值的时候,比如说元素B[j][i],由于上一次赋值的时候用到过B的B[j][i-1],所以B[j][i]已经缓存在cache的对应行中了,当执行完第一列后,后面的7列就都会命中,这样命中率就会大大提升。
因为这样每次都会用到缓存的数据,所以效率会高,如果直接按照正常顺序赋值,虽然第二个八个循环的时候会用到相同的块,但是块中缓存的数据却不是我们需要用的数据。
这样分块之后,如果在每个块内用两层循环直接赋值的话,miss是343次,还是不能达到要求。注意到当A[i][j]和B[j][i]所用的块是相同的时候,A缓存之后,调用B[j][i]的时候会冲掉缓存的A,这样再进行下一次A[i][j+1]的时候,会导致miss数多一次。所以对于每一行的赋值,我们采取手动赋值,先存出来A的所有8个值,再一起赋给B。
c o d e code code
int i,j,k;
int o0,o1,o2,o3,o4,o5,o6,o7;
for(i=0;i<N;i+=8)
for(j=0;j<M;j+=8)
for(k=0;k<8;++k){
o0=A[i+k][j];o1=A[i+k][j+1];
o2=A[i+k][j+2];o3=A[i+k][j+3];
o4=A[i+k][j+4];o5=A[i+k][j+5];
o6=A[i+k][j+6];o7=A[i+k][j+7];
B[j][i+k]=o0;B[j+1][i+k]=o1;
B[j+2][i+k]=o2;B[j+3][i+k]=o3;
B[j+4][i+k]=o4;B[j+5][i+k]=o5;
B[j+6][i+k]=o6;B[j+7][i+k]=o7;
}
**
**
64X64与之前的32X32的大致思路差不多,但是不同的是,64X64的中一行有8个块,所以cache只能缓存4行的信息,如果简单的再用8X8的矩阵分分割的方法的话,就会造成很多的不命中数量。
第一个想法是用更小的矩阵进行分割,因为cache只能保存4行,所以就用4X4的矩阵进行分割。
c o d e code code
for(i=0;i<N;i+=4)
for(j=0;j<M;j+=4)
for(k=0;k<4;++k){
o0=A[i+k][j];o1=A[i+k][j+1];
o2=A[i+k][j+2];o3=A[i+k][j+3];
B[j][i+k]=o0;B[j+1][i+k]=o1;
B[j+2][i+k]=o2;B[j+3][i+k]=o3;
}
这样的miss次数为1699,还是不能达到1300次的要求。这样效率不够的原因主要是当读取A中的元素的时候,由于我们在cache中缓存的8个元素,但是每次我们只用了4个,这样就没有充分利用缓存的元素,造成了浪费。
所以我们还是按照8X8的矩阵来进行分割。与之前不同的是,我们将8X8的矩阵分成4个4X4的小矩阵。
首先我们在A中取出每一行缓存的8个元素。对于左侧的A中的左上角矩阵中的元素,我们按照正常的转置的方式放入B中,也就是红色剪头所示的方式。对于A中右上角的元素,由于在之前的操作中,B中的前四行也缓存在了cache中,所以我们先将A中右上角的元素放入B的右上角,也就是绿色箭头所示的操作。这样在处理前四行的过程中我们就可以充分利用cache的所有缓存。
然后我们再对B中的右上角的矩阵和左下角的矩阵做正确的处理。先将A中左下角矩阵中的一列和B中右上角矩阵中的一样拿出来。然后将这8个元素分别存在B中正确的位置。这里如果先去A中的蓝色箭头,再取B中右上角的绿色箭头,然后将A中的蓝色箭头保存到B中,再保存绿色箭头的元素。这种操作在第三步的时候可以利用到第二步中缓存的数据。
最后我们按照正常的方法处理右下角矩阵,就可以将miss次数控制在1300之内了。
后来,在处理右下角的时候,我又想到了一种可以优化的方法:
之前在处理右下角的时候,我又单独循环了一遍。但是在第二个循环中可以发现,在最后给B中右下角矩阵的绿色元素赋值之后,还将红色剪头的元素缓存到了cache中,而这些元素在处理右下角矩阵的时候恰好可以用到。所以我们用变量在第二个循环中先存下来A中红色剪头对应列的元素,在做完B中右下角矩阵赋值之后,接着给B中红色剪头的元素赋值,这样又增加了缓存数据的利用率。事实证明这样的miss次数可以降到1139次。
c o d e code code
for(i=0;i<N;i+=8)
for(j=0;j<M;j+=8){
for(k=0;k<4;++k){
o0=A[i+k][j];o1=A[i+k][j+1];
o2=A[i+k][j+2];o3=A[i+k][j+3];
o4=A[i+k][j+4];o5=A[i+k][j+5];
o6=A[i+k][j+6];o7=A[i+k][j+7];
B[j][i+k]=o0;B[j+1][i+k]=o1;
B[j+2][i+k]=o2;B[j+3][i+k]=o3;
B[j][i+k+4]=o4;B[j+1][i+k+4]=o5;
B[j+2][i+k+4]=o6;B[j+3][i+k+4]=o7;
}
for(k=0;k<4;++k){
o0=A[i+4][j+k];o1=A[i+5][j+k];
o2=A[i+6][j+k];o3=A[i+7][j+k];
o4=B[j+k][i+4];o5=B[j+k][i+5];
o6=B[j+k][i+6];o7=B[j+k][i+7];
B[j+k][i+4]=o0;B[j+k][i+5]=o1;
B[j+k][i+6]=o2;B[j+k][i+7]=o3;
o0=A[i+4][j+k+4];o1=A[i+5][j+k+4];
o2=A[i+6][j+k+4];o3=A[i+7][j+k+4];
B[j+k+4][i]=o4;B[j+k+4][i+1]=o5;
B[j+k+4][i+2]=o6;B[j+k+4][i+3]=o7;
B[j+k+4][i+4]=o0;B[j+k+4][i+5]=o1;
B[j+k+4][i+6]=o2;B[j+k+4][i+7]=o3;
}
**
由于这个的限制比较松,只用在2000次miss以内就行。
所以直接用32的思路,只是这个按照8X8的分块过后,只能在64X56的矩形区域内形成完整的块。还剩下了两个长条的区域。如果直接暴力处理这两个区域的话,是不符合要求的。
如上图所示,我们先用32X32的方法处理红色区域的部分。对于两个蓝色区域,由于不足一个完整的块,所以用类似于8X8的方法,手动设置边界来翻转。最后绿色部分直接暴力翻转,这样总的miss数就在2000之下了。
for(i=0;i<64;i+=8)
for(j=0;j<56;j+=8)
for(k=0;k<8;++k){
o0=A[i+k][j];o1=A[i+k][j+1];
o2=A[i+k][j+2];o3=A[i+k][j+3];
o4=A[i+k][j+4];o5=A[i+k][j+5];
o6=A[i+k][j+6];o7=A[i+k][j+7];
B[j][i+k]=o0;B[j+1][i+k]=o1;
B[j+2][i+k]=o2;B[j+3][i+k]=o3;
B[j+4][i+k]=o4;B[j+5][i+k]=o5;
B[j+6][i+k]=o6;B[j+7][i+k]=o7;
}
for(j=0;j<56;j+=8)
for(k=64;k<N;++k){
o0=A[k][j];o1=A[k][j+1];
o2=A[k][j+2];o3=A[k][j+3];
o4=A[k][j+4];o5=A[k][j+5];
o6=A[k][j+6];o7=A[k][j+7];
B[j][k]=o0;B[j+1][k]=o1;
B[j+2][k]=o2;B[j+3][k]=o3;
B[j+4][k]=o4;B[j+5][k]=o5;
B[j+6][k]=o6;B[j+7][k]=o7;
}
for(i=0;i<64;i+=8)
for(k=0;k<8;++k){
o0=A[i+k][56];o1=A[i+k][57];
o2=A[i+k][58];o3=A[i+k][59];
o4=A[i+k][60];
B[56][i+k]=o0;B[57][i+k]=o1;
B[58][i+k]=o2;B[59][i+k]=o3;
B[60][i+k]=o4;
}
for(i=64;i<N;++i)
for(j=56;j<M;++j)
B[j][i]=A[i][j];