[CASPP][CACHE]实验

由于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;
}

**

32 X 32 32X32 32X32

**
首先应当先了解一下A和B中的每一个元素对应到cache中的哪一个块中:
[CASPP][CACHE]实验_第1张图片
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列就都会命中,这样命中率就会大大提升。
[CASPP][CACHE]实验_第2张图片
因为这样每次都会用到缓存的数据,所以效率会高,如果直接按照正常顺序赋值,虽然第二个八个循环的时候会用到相同的块,但是块中缓存的数据却不是我们需要用的数据。
这样分块之后,如果在每个块内用两层循环直接赋值的话,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;
		}

**

64 X 64 64X64 64X64

**
64X64与之前的32X32的大致思路差不多,但是不同的是,64X64的中一行有8个块,所以cache只能缓存4行的信息,如果简单的再用8X8的矩阵分分割的方法的话,就会造成很多的不命中数量。
第一个想法是用更小的矩阵进行分割,因为cache只能保存4行,所以就用4X4的矩阵进行分割。
[CASPP][CACHE]实验_第3张图片

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;
		}

[CASPP][CACHE]实验_第4张图片
这样的miss次数为1699,还是不能达到1300次的要求。这样效率不够的原因主要是当读取A中的元素的时候,由于我们在cache中缓存的8个元素,但是每次我们只用了4个,这样就没有充分利用缓存的元素,造成了浪费。
所以我们还是按照8X8的矩阵来进行分割。与之前不同的是,我们将8X8的矩阵分成4个4X4的小矩阵。
[CASPP][CACHE]实验_第5张图片
首先我们在A中取出每一行缓存的8个元素。对于左侧的A中的左上角矩阵中的元素,我们按照正常的转置的方式放入B中,也就是红色剪头所示的方式。对于A中右上角的元素,由于在之前的操作中,B中的前四行也缓存在了cache中,所以我们先将A中右上角的元素放入B的右上角,也就是绿色箭头所示的操作。这样在处理前四行的过程中我们就可以充分利用cache的所有缓存。
[CASPP][CACHE]实验_第6张图片
然后我们再对B中的右上角的矩阵和左下角的矩阵做正确的处理。先将A中左下角矩阵中的一列和B中右上角矩阵中的一样拿出来。然后将这8个元素分别存在B中正确的位置。这里如果先去A中的蓝色箭头,再取B中右上角的绿色箭头,然后将A中的蓝色箭头保存到B中,再保存绿色箭头的元素。这种操作在第三步的时候可以利用到第二步中缓存的数据。
最后我们按照正常的方法处理右下角矩阵,就可以将miss次数控制在1300之内了。
后来,在处理右下角的时候,我又想到了一种可以优化的方法:
[CASPP][CACHE]实验_第7张图片
之前在处理右下角的时候,我又单独循环了一遍。但是在第二个循环中可以发现,在最后给B中右下角矩阵的绿色元素赋值之后,还将红色剪头的元素缓存到了cache中,而这些元素在处理右下角矩阵的时候恰好可以用到。所以我们用变量在第二个循环中先存下来A中红色剪头对应列的元素,在做完B中右下角矩阵赋值之后,接着给B中红色剪头的元素赋值,这样又增加了缓存数据的利用率。事实证明这样的miss次数可以降到1139次。
[CASPP][CACHE]实验_第8张图片
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;
		}

ps:用64的这个代码跑32的代码miss还要少
[CASPP][CACHE]实验_第9张图片
**

67 X 61 67X61 67X61

**
由于这个的限制比较松,只用在2000次miss以内就行。
所以直接用32的思路,只是这个按照8X8的分块过后,只能在64X56的矩形区域内形成完整的块。还剩下了两个长条的区域。如果直接暴力处理这两个区域的话,是不符合要求的。

[CASPP][CACHE]实验_第10张图片
如上图所示,我们先用32X32的方法处理红色区域的部分。对于两个蓝色区域,由于不足一个完整的块,所以用类似于8X8的方法,手动设置边界来翻转。最后绿色部分直接暴力翻转,这样总的miss数就在2000之下了。
[CASPP][CACHE]实验_第11张图片

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];

最后放一个完整版的test
[CASPP][CACHE]实验_第12张图片

你可能感兴趣的:(CSAPP)