哈工大计算机系统实验六——高速缓冲器模拟

实验报告

验(六)

题     目      Cachelab        

    高速缓冲器模拟 

专       业    xxxx             

学     号    xxxx           

班     级    xxxx              

学       生    xxxx            

指 导 教 师    xxxx              

实 验 地 点   G712              

实 验 日 期   2019.12.7          

计算机科学与技术学院

目  录

 

第1章 实验基本信息............................................................................................. - 3 -

1.1 实验目的......................................................................................................... - 3 -

1.2 实验环境与工具............................................................................................. - 3 -

1.2.1 硬件环境................................................................................................. - 3 -

1.2.2 软件环境................................................................................................. - 3 -

1.2.3 开发工具................................................................................................. - 3 -

1.3 实验预习......................................................................................................... - 3 -

第2章 实验预习..................................................................................................... - 4 -

2.1 画出存储器层级结构,标识容量价格速度等指标变化(5分).............. - 4 -

2.2用CPUZ等查看你的计算机Cache各参数,写出各级Cache的C S E B s e b(5分)........................................................................................................... - 4 -

2.3写出各类Cache的读策略与写策略(5分)............................................. - 4 -

2.4 写出用gprof进行性能分析的方法(5分).............................................. - 4 -

2.5写出用Valgrind进行性能分析的方法((5分)...................................... - 4 -

第3章 Cache模拟与测试................................................................................. - 5 -

3.1 Cache模拟器设计......................................................................................... - 5 -

3.2 矩阵转置设计................................................................................................. - 5 -

第4章 总结............................................................................................................. - 6 -

4.1 请总结本次实验的收获................................................................................. - 6 -

4.2 请给出对本次实验内容的建议..................................................................... - 6 -

参考文献................................................................................................................... - 7 -

 

 

 

 

第1章 实验基本信息

 

1.1 实验目的

理解现代计算机系统存储器层级结构

掌握Cache的功能结构与访问控制策略

培养Linux下的性能测试方法与技巧

深入理解Cache组成结构对C程序性能的影响

1.2 实验环境与工具

1.2.1 硬件环境

X64 CPU2GHz2G RAM256GHD Disk 以上

1.2.2 软件环境

Windows7 64位以上;VirtualBox/Vmware 11以上;Ubuntu 16.04 LTS 64/优麒麟 64位;

1.2.3 开发工具

Visual Studio 2010 64位以上;TestStudioGprof;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;

  1. 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));//为行申请空间

 

  1. 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×3210分):运行结果截图

 

64×6410分):运行结果截图

 

61×6720分):运行结果截图

 

第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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值