实验报告
实 验(六)
题 目 Cachelab
高速缓冲器模拟
专 业 xxxx
学 号 xxxx
班 级 xxxx
学 生 xxxx
指 导 教 师 xxxx
实 验 地 点 G712
实 验 日 期 2019.12.7
计算机科学与技术学院
目 录
2.1 画出存储器层级结构,标识容量价格速度等指标变化(5分).............. - 4 -
2.3写出各类Cache的读策略与写策略(5分)............................................. - 4 -
2.4 写出用gprof进行性能分析的方法(5分).............................................. - 4 -
2.5写出用Valgrind进行性能分析的方法((5分)...................................... - 4 -
4.2 请给出对本次实验内容的建议..................................................................... - 6 -
第1章 实验基本信息
1.1 实验目的
1.2 实验环境与工具
1.2.1 硬件环境
X64 CPU;2GHz;2G RAM;256GHD Disk 以上
1.2.2 软件环境
Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64位/优麒麟 64位;
1.2.3 开发工具
Visual Studio 2010 64位以上;TestStudio;Gprof;Valgrind等
1.3 实验预习
上实验课前,必须认真预习实验指导书(PPT或PDF)
了解实验的目的、实验环境与软硬件工具、实验操作步骤,复习与实验有关的理论知识。
画出存储器的层级结构,标识其容量价格速度等指标变化
用CPUZ等查看你的计算机Cache各参数,写出C S E B s e b
写出Cache的基本结构与参数
写出各类Cache的读策略与写策略
掌握Valgrind与Gprof的使用方法
第2章 实验预习
2.1 画出存储器层级结构,标识容量价格速度等指标变化(5分)
2.2用CPUZ等查看你的计算机Cache各参数,写出各级Cache的C S E B s e b(5分)
C S E B s e
一级数据缓存: 32KB 8 64 8 3 6
一级指令缓存: 32KB 8 64 8 3 6
二级指令缓存: 256KB 4 64 128 2 6
三级指令缓存: 9MB 12 64 12288 log12 6
2.3写出各类Cache的读策略与写策略(5分)
Cache读策略
1:缓存命中,则从cache中读相应数据到CPU或上一级cache中。
2:缓存不命中,则从主存或下一级cache中读取数据,并替换出一行数据。
Cache写策略
1、写回
当CPU写Cache命中时,只修改Cache的内容,而不是立即写入主存;只有当此块被换出时才写回主存。
2、直写
立即将一个已经缓存了的字w的高速缓存块写回到紧接着的第一层中。。
3、写分配
加载相应的低一层的块到高速缓存中,然后更新这个高速缓存块。
4、非写分配
避开高速缓存,直接把这个字写到低一层中去。
2.4 写出用gprof进行性能分析的方法(5分)
gprof是GNU profile工具,可以运行于linux、AIX、Sun等操作系统进行C、C++、Pascal、Fortran程序的性能分析,用于程序的性能优化以及程序瓶颈问题的查找和解决。通过分析应用程序运行时产生的“flat profile”,可以得到每个函数的调用次数,每个函数消耗的处理器时间,也可以得到函数的“调用关系图”,包括函数调用的层次关系,每个函数调用花费了多少时间。使用步骤如下:
(1)用gcc、g++、xlC编译程序时,使用-pg参数,如:g++ -pg -o test.exe test.cpp编译器会自动在目标代码中插入用于性能测试的代码片断,这些代码在程序运行时采集并记录函数的调用关系和调用次数,并记录函数自身执行时间和被调用函数的执行时间。
(2)执行编译后的可执行程序,如:./test.exe。该步骤运行程序的时间会稍慢于正常编译的可执行程序的运行时间。程序运行结束后,会在程序所在路径下生成一个缺省文件名为gmon.out的文件,这个文件就是记录程序运行的性能、调用关系、调用次数等信息的数据文件。
(3)使用gprof命令来分析记录程序运行信息的gmon.out文件,如:gprof test.exe gmon.out则可以在显示器上看到函数调用相关的统计、分析信息。上述信息也可以采用gprof test.exe gmon.out> gprofresult.txt重定向到文本文件以便于后续分析.
2.5写出用Valgrind进行性能分析的方法(5分)
Valgrind是运行在Linux上一套基于仿真技术的程序调试和分析工具,它包含一个内核──一个软件合成的CPU,和一系列的小工具,每个工具都可以完成一项任务──调试,分析,或测试等。Valgrind可以检测内存泄漏和内存违例,还可以分析cache的使用等。Valgrind包含以下工具:Memcheck(用来检测程序中出现的内存问题,所有对内存的读写都会被检测到,一切对malloc()/free()/new/delete的调用都会被捕获)、Callgrind(收集程序运行时的一些数据,建立函数调用关系图,还可以有选择地进行cache模拟。在运行结束时,它会把分析数据写入一个文件,callgrind_annotate可以把这个文件的内容转化成可读的形式)、Cachegrind(模拟CPU中的一级缓存I1,Dl和二级缓存,能够精确地指出程序中cache的丢失和命中。如果需要,它还能够为我们提供cache丢失次数,内存引用次数,以及每行代码,每个函数,每个模块,整个程序产生的指令数)、Helgrind(用来检查多线程程序中出现的竞争问题)、Massif(堆栈分析器,能测量程序在堆栈中使用了多少内存,告诉我们堆块,堆管理块和栈的大小)。Valgrind的使用非常简单,valgrind命令的格式如下:valgrind [valgrind-options] your-prog [your-prog options] 。一些常用的选项如下:
选项 作用
-h --help 显示帮助信息
--version 显示valgrind内核的版本,每个工具都有各自的 版本
-q --quiet 安静地运行,只打印错误信息
-v --verbose 打印更详细的信息。
--tool= [default: memcheck] 最常用的选项。运行valgrind中名为toolname 的工具。如果省略工具名,默认运行memcheck。
--db-attach= [default: no] 绑定到调试器上,便于调试错误。
第3章 Cache模拟与测试
3.1 Cache模拟器设计
提交csim.c(如下图所示)
/*csim.c 源代码*/
#include <getopt.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <assert.h>
#include <math.h>
#include <limits.h>
#include <string.h>
#include <errno.h>
#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; //标识位
int lru; //最后的访问时间距离现在最远的块
} cache_line_t;
typedef struct
{
cache_line_t *lines;
}cache_set_t; //储存每一组包含的行
typedef struct
{
cache_set_t *sets; //cache的空间,模拟cache
}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; //组索引掩码
mem_addr_t tag_mask; //地址标记
/*
* initCache - Allocate memory, write 0's for valid and tag and LRU
* also computes the set_index_mask
initCache - 分配内存,写0表示有效和标记和LRU ,计算set_index_mask
*/
void initCache()
{
int i,j;
if(s<0)
{
printf("error!\n");
exit(0);
}
cache.sets = (cache_set_t*)malloc(S*sizeof(cache_set_t));//为组申请空间
if(!cache.sets)
{
printf("No set memory!\n");
exit(0);
}
for(i=0;i<S;i++)
{
cache.sets[i].lines = (cache_line_t*)malloc(E*sizeof(cache_line_t));//为行申请空间
if(!cache.sets)
{
printf("No line memory!\n");
exit(0);
}
for(j=0;j<E;j++)
{
cache.sets[i].lines[j].lru=0;
cache.sets[i].lines[j].tag=0;
cache.sets[i].lines[j].valid=0;
}
}
}
/*
* freeCache - free allocated memory
*/
void freeCache()
{
int i;
for(i=0;i<S;i++)
{
free(cache.sets[i].lines);
}
free(cache.sets);
}
/*
* 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.
accessData - 访问内存地址addr的数据。
*如果它已经在缓存中,则增加hit_count
*如果它不在缓存中,请将其放入缓存中,增加错过次数。
*如果一条线被驱逐,也会增加eviction_count
*/
void accessData(mem_addr_t addr)
{
int i, isfull, j ,k , flag , minlru;
isfull=1;
for(i=0;i<E;i++)
{
if(cache.sets[set_index_mask].lines[i].valid==1\
&&cache.sets[set_index_mask].lines[i].tag==tag_mask)
{
hit_count++;
cache.sets[set_index_mask].lines[i].lru=lru_counter;
for(k=0;k<E;k++)
{
if(k!=i)
cache.sets[set_index_mask].lines[k].lru--;
}
return;
}
}
miss_count++;
for(i=0;i<E;i++)
{
if(cache.sets[set_index_mask].lines[i].valid==0)
{
isfull=0;
break;
}
}
if(isfull==0) //未满
{
cache.sets[set_index_mask].lines[i].valid=1;
cache.sets[set_index_mask].lines[i].tag=tag_mask;
cache.sets[set_index_mask].lines[i].lru=lru_counter;
for(k=0;k<E;k++)
{
if(k!=i)
cache.sets[set_index_mask].lines[k].lru--;
}
}
else
{
eviction_count++;
flag = 0;
minlru = cache.sets[set_index_mask].lines[0].lru;
for(j=0;j<E;j++)
{
if(minlru>cache.sets[set_index_mask].lines[j].lru)
{
minlru = cache.sets[set_index_mask].lines[j].lru;
flag = j;
}
}
cache.sets[set_index_mask].lines[flag].valid=1;
cache.sets[set_index_mask].lines[flag].tag = tag_mask;
cache.sets[set_index_mask].lines[flag].lru=lru_counter;
for(k=0;k<E;k++)
{
if(k!=flag)
cache.sets[set_index_mask].lines[k].lru--;
}
}
}
/*
* replayTrace - replays the given trace file against the cache
replayTrace - 针对缓存重放给定的跟踪文件
*/
void replayTrace(char* trace_fn)
{
char buf[1000];
mem_addr_t addr=0;
tag_mask=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);
set_index_mask =(addr>>b)&((1<<s)-1); //组索引
tag_mask = (addr>>b)>>s; //标记
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 <num> -E <num> -b <num> -t <file>\n", argv[0]);
printf("Options:\n");
printf(" -h Print this help message.\n");
printf(" -v Optional verbose flag.\n");
printf(" -s <num> Number of set index bits.\n");
printf(" -E <num> Number of lines per set.\n");
printf(" -b <num> Number of block offset bits.\n");
printf(" -t <file> 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,E和B. */
S = 1<<s; //组数
B = 1<<b; //块大小
E = E;
/* Initialize cache */
initCache();
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;
}
程序设计思想:
1.分析一下实验的内容和需要做的事
通过ppt可知,我们需要编写一个cache模拟器,该模拟器可以模拟在一系列的数据访问中cache的命中、不命中与牺牲行的情况,其中,需要牺牲行时,用LRU替换策略进行替换。
cache模拟器需要能处理一系列如下的命令:
Usage: ./csim-ref [-hv] -s <s> -E <E> -b <b> -t <tracefile>
其中各参数意义如下:
①-h:输出帮助信息的选项;
②-v:输出详细运行过程信息的选项;
③-s:组索引的位数(意味着组数S=2^s);
④-E:每一组包含的行数;
⑤-b:偏移位的宽度(意味着块的大小为B=2^b);
⑥-t:输入数据文件的路径(测试数据从该文件里面读取)。
2.分析测试数据
traces文件夹里面包含五个文件:
这五个文件就是用于测试csim.c的输入文件,各个文件中包含了各种不同指令,用于测试hits、missses、evictons。
trace文件中的指令具有如下形式:
I 0400d7d4,8
M 0421c7f0,4
L 04f6b868,8
S 7ff0005c8,8
即每行代表一个或两个内存访问。每行的格式是
[空格]操作 地址,大小
操作字段表示存储器访问的类型,其中:
“I”表示指令加载,
“L”表示数据加载,
“S”表示数据存储,
“M”表示数据修改(即数据存储之后的数据加载)。
每个“I”前面都没有空格。每个“M”,“L”和“S”之前总是有空格。
地址字段指定一个32位的十六进制存储器地址。
大小字段指定操作访问的字节数;
通俗地解释一下各种操作:
①对于‘I’指定地操作,实验说明中提到,我们不需要考虑:
意思就是valgrind运行地时候第一个指令总是为操作‘I’。
②对于‘L’以及‘S’指定的操作,我们简单地可以认为这两个操作都是对某一个地址寄存器进行访问(读取或者存入数据);
③对于‘M’指定的操作,可以看作是对于同一地址连续进行‘L‘和’S‘操作。
以yi.trace中的数据为例:
其解释如下:
①对于地址0x10进行访问
0x10=0000...00010000,偏移值为最低四位,故S=1;
访问结果为mis;
②连续对地址0x20进行连续两次访问:
0x20=000...00100000,S=2;
结果为第一次mis,第二次hit;
③对地址0x22进行访问:
0x22=000...00100100,S=2;
由于操作②以将该块存入高速缓存,故结果为hit;
④对地址0x18进行访问:
0x18=000...00011000,S=1;
由于操作①以将该块存入高速缓存,故结果为hit;
⑤对地址0x110进行访问:
0x110=0...000100010000,S=1;
虽然操作①使得第一组(只有一行有效),但是这里的标志位的值Tag为1
故结果为先mis,后eviction;
⑥对地址0x210进行访问:
0x210=0...001000010000,S=1;
同操作⑤,但是这里的标志位的值为2,不匹配
故结果为先mis,后evicton;
⑦对地址0x12进行连续两次访问:
0x12=000...00000010010,S=1;
由于标志位不匹配,故第一次访问时mis,并evicton
第二次访问时当然就是hit。
上述分析也就是解释了实验说明中的示例:
3.分析一下csim.c的主要函数功能
(1)Cache结构体的声明:
(2)分析主要函数:
在主函数中从命令行参数计算S,E和B. 如下:
S = 1<<s; //组数
B = 1<<b; //块大小
E = E;
- initCache()函数 - 分配内存,写0表示有效和标记和LRU,为它们初始化
cache.sets = (cache_set_t*)malloc(S*sizeof(cache_set_t));
cache.sets = (cache_set_t*)malloc(S*sizeof(cache_set_t));//为组申请空间
cache.sets[i].lines = (cache_line_t*)malloc(E*sizeof(cache_line_t));//为行申请空间
- freeCache()函数:为释放空间,根据申请空间的倒序来释放即可。
(3)void replayTrace(char* trace_fn) :此函数基本已经全部给出,主要的就是从trace文件中读取数据,并且调用accessdata函数,操作类型若为 'L'或 'S',则调用一次accessdata,若为 'M' ,则多调用一次accessdata 。 另外在次函数中读取了地址addr之后,可以计算出组索引和标记:
set_index_mask =(addr>>b)&((1<<s)-1); //组索引
tag_mask = (addr>>b)>>s; //标记
(4)accessData - 访问内存地址addr的数据。
1)如果它已经在缓存中,则增加hit_count
2)如果它不在缓存中,请将其放入缓存中,增加错过次数。
3)如果一条线被驱逐,也会增加eviction_count
在函数中实现时,hit发生的情况:组索引找到的某一组,存在一行有效位为1,并且标记匹配。
若不hit ,则直接miss++。
再看是否驱逐,驱逐发生的情况为:组索引找到的某一组,有效位全部为1,此时发生evictions++ ,并且找到lru最小的那一行,驱逐。
另外,每次发生hit 或者只miss或者miss加上eviction ,都需要更新那一行的lru数值,具体的就是该行的lru取到最大,其他所有行的lru减一即可
4.程序的测试
测试用例1的输出截图(5分):
测试用例2的输出截图(5分):
测试用例3的输出截图(5分):
测试用例4的输出截图(5分):
测试用例5的输出截图(5分):
测试用例6的输出截图(5分):
测试用例7的输出截图(5分):
测试用例8的输出截图(10分):
注:每个用例的每一指标5分(最后一个用例10)——与参考csim-ref模拟器输出指标相同则判为正确
3.2 矩阵转置设计
/*
* trans.c - Matrix transpose B = A^T
*
* Each transpose function must have a prototype of the form:
* void trans(int M, int N, int A[N][M], int B[M][N]);
*
* A transpose function is evaluated by counting the number of misses
* on a 1KB direct mapped cache with a block size of 32 bytes.
*/
#include <stdio.h>
#include "cachelab.h"
int is_transpose(int M, int N, int A[N][M], int B[M][N]);
/*
* transpose_submit - This is the solution transpose function that you
* will be graded on for Part B of the assignment. Do not change
* the description string "Transpose submission", as the driver
* searches for that string to identify the transpose function to
* be graded.
*/
char transpose_submit_desc[] = "Transpose submission";
void transpose_submit(int M, int N, int A[N][M], int B[M][N])
{
int i,j,k,p,t0,t1,t2,t3,t4,t5,t6,t7,t8;
if(M==32&&N==32)
{
for(i=0;i<M;i=i+8)
{
for(j=0;j<N;j++)
{
t1 = A[j][i];
t2 = A[j][i+1];
t3 = A[j][i+2];
t4 = A[j][i+3];
t5 = A[j][i+4];
t6 = A[j][i+5];
t7 = A[j][i+6];
t8 = A[j][i+7];
B[i][j] = t1;
B[i+1][j] = t2;
B[i+2][j] = t3;
B[i+3][j] = t4;
B[i+4][j] = t5;
B[i+5][j] = t6;
B[i+6][j] = t7;
B[i+7][j] = t8;
}
}
}
for(i=0;i<64;i+=8){
for(j=0;j<64;j+=8){
for(k=j;k<j+4;k++){
t0=A[k][i];
t1=A[k][i+1];
t2=A[k][i+2];
t3=A[k][i+3];
t4=A[k][i+4];
t5=A[k][i+5];
t6=A[k][i+6];
t7=A[k][i+7];
B[i][k]=t0;
B[i][k+4]=t4;
B[i+1][k]=t1;
B[i+1][k+4]=t5;
B[i+2][k]=t2;
B[i+2][k+4]=t6;
B[i+3][k]=t3;
B[i+3][k+4]=t7;
}
for(k=i;k<i+4;k++){
t0=B[k][j+4];
t1=B[k][j+5];
t2=B[k][j+6];
t3=B[k][j+7];
t4=A[j+4][k];
t5=A[j+5][k];
t6=A[j+6][k];
t7=A[j+7][k];
B[k][j+4]=t4;
B[k][j+5]=t5;
B[k][j+6]=t6;
B[k][j+7]=t7;
B[k+4][j]=t0;
B[k+4][j+1]=t1;
B[k+4][j+2]=t2;
B[k+4][j+3]=t3;
}
for(k=i+4;k<i+8;k++){
t0=A[j+4][k];
t1=A[j+5][k];
t2=A[j+6][k];
t3=A[j+7][k];
B[k][j+4]=t0;
B[k][j+5]=t1;
B[k][j+6]=t2;
B[k][j+7]=t3;
}
}
}
if(M==61&&N==67)
{
for(i=0;i<N;i=i+17)
{
for(j=0;j<M;j=j+17)
{
for(k=i;k<i+17 && k<N;k++)
{
for(p=j;p<j+17 && p<M;p++)
{
t1 = A[k][p];
B[p][k] = t1;
}
}
}
}
}
}
/*
* You can define additional transpose functions below. We've defined
* a simple one below to help you get started.
*/
/*
* trans - A simple baseline transpose function, not optimized for the cache.
*/
char trans_desc[] = "Simple row-wise scan transpose";
void trans(int M, int N, int A[N][M], int B[M][N])
{
int i, j, tmp;
for (i = 0; i < N; i++) {
for (j = 0; j < M; j++) {
tmp = A[i][j];
B[j][i] = tmp;
}
}
}
/*
* registerFunctions - This function registers your transpose
* functions with the driver. At runtime, the driver will
* evaluate each of the registered functions and summarize their
* performance. This is a handy way to experiment with different
* transpose strategies.
*/
void registerFunctions()
{
/* Register your solution function */
registerTransFunction(transpose_submit, transpose_submit_desc);
/* Register any additional transpose functions */
registerTransFunction(trans, trans_desc);
}
/*
* is_transpose - This helper function checks if B is the transpose of
* A. You can check the correctness of your transpose by calling
* it before returning from the transpose function.
*/
int is_transpose(int M, int N, int A[N][M], int B[M][N])
{
int i, j;
for (i = 0; i < N; i++) {
for (j = 0; j < M; ++j) {
if (A[i][j] != B[j][i]) {
return 0;
}
}
}
return 1;
}
提交trans.c()
程序设计思想:
1.分析一下题干内容和要做的事情
①编写一个实现矩阵转置的函数。即对于给定的矩阵A[N][M],得到矩阵B[M][N],使得对于任意0<=i<N、0<=j<M,有B[j][i]=A[i][j],
并且使函数调用过程中对cache的不命中数miss尽可能少。
②在如下函数里面编写最终代码:
char transpose_submit_desc[] = "Transpose submission";
void transpose_submit(int M, int N, int A[N][M], int B[M][N]);
③测试用例:
用三种不同规模的数组进行测试,规模分别为:
• 32 × 32 (M = 32, N = 32)
• 64 × 64 (M = 64, N = 64)
• 61 × 67 (M = 61, N = 67)
2.开始实现三种不同规模的数组转置
(1)32*32
因为A矩阵和B矩阵中下标相同的元素会映射到一个块中,因此访问两个数组的过程中会发生很多的冲突不命中。因此要减少miss,必须减少冲突不命中。基于这个原因,可以想到,我们可以一次性访问一个块中的多个元素,访问完就不再访问这个块了。
因为cache一个block中可以存8个int数据,因此先考虑8*8将其分块,这样分块的一个更重要的原因是当我将8个int直接取出来,这样即使写入的时候替换了block也没关系,因为我们已经全部读入了。
测试一下
嗯呢,达到要求了,那试试64X64能不能这样做
(2)64*64
嗯实际是行不通的,对A数组的访问依然是第一个不命中。对B数组的访问,可以看到前4行和后四行所映射的块是相同的,于是访问完前四行的第一列后,访问后四行的第一列会冲突不命中,导致原来的块被驱逐,再访问前四行的第二列,由于之前的块已经被驱逐,因此又会miss且驱逐,如此反复下去,B数组中所有的元素皆会不命中。由以上否定了8*8的划分方式。
思路:每次处理一个块,也就是像32x32那样取出每行的8个元素,然后将 每行的0-3号元素正常的放到B[0-3][0]这些位置去(也就是正常的转置),剩下的四个元先放置B矩阵4x4块的最右边保存,注意这个时候我已经取出来了。然后再用一个循环去把这些值放到B正确的位置去,然后A[4]到A[7]也是上述的处理。对整个矩阵重复这个操作即可。简单的示意图如下:
(3)61x67
于之前的两个矩阵,由于它们的每一行元素恰好占整数个块,因此分块的时候也会利用这一特性。但是根据之前的处理,由于这个测试的miss数允许到2000,这个限度相对较大,因此我们可以尝试不同的分块来处理即可。因此相当于64*64需要反复优化,这个还是比较好处理的。
调整分块的大小,测试了四组,最终确定17是最好的。
17x17
32×32(10分):运行结果截图
64×64(10分):运行结果截图
61×67(20分):运行结果截图
第4章 总结
4.1 请总结本次实验的收获
理解了现代计算机系统存储器层级结构
掌握了Cache的功能结构与访问控制策略
培养了Linux下的性能测试方法与技巧
深入理解了Cache组成结构对C程序性能的影响
以后会充分考虑Cache的功能结构来书写程序,减少Cache不命中的次数。
4.2 请给出对本次实验内容的建议
建议老师多给点时间完成这个实验
注:本章为酌情加分项。
参考文献
为完成本次实验你翻阅的书籍与网站等
[1] 林来兴. 空间控制技术[M]. 北京:中国宇航出版社,1992:25-42.
[2] 辛希孟. 信息技术与信息服务国际研讨会论文集:A集[C]. 北京:中国科学出版社,1999.
[3] 赵耀东. 新时代的工业工程师[M/OL]. 台北:天下文化出版社,1998 [1998-09-26]. http://www.ie.nthu.edu.tw/info/ie.newie.htm(Big5).
[4] 谌颖. 空间交会控制理论与方法研究[D]. 哈尔滨:哈尔滨工业大学,1992:8-13.
[5] KANAMORI H. Shaking Without Quaking[J]. Science,1998,279(5359):2063-2064.
[6] CHRISTINE M. Plant Physiology: Plant Biology in the Genome Era[J/OL]. Science,1998,281:331-332[1998-09-23]. http://www.sciencemag.org/cgi/ collection/anatmorp.