CSAPP的Lab学习——CacheLab

文章目录

  • 前言
  • 一、A部分:编写一个高速缓存模拟器
    • 构造高速缓冲行结构
    • 仿写主函数,使用getopt()函数
    • 分配空间并释放
    • 读取给的trace文件
    • 模拟cache行为
  • 二、B部分:优化矩阵转置
    • 32 * 32矩阵转置
    • 64 * 64矩阵转置
    • 61 * 67矩阵转置
  • 总结


前言

一个本硕双非的小菜鸡,备战24年秋招。刚刚看完CSAPP,真是一本神书啊!遂尝试将它的Lab实现,并记录期间心酸历程。
代码下载

官方网站:CSAPP官方网站

以下是官方文档翻译:
这个实验室将帮助您了解缓存存储器对C程序性能的影响。
这个实验室由两部分组成。在第一部分中,您将编写一个小的C程序(大约200-300行)来模拟缓存内存的行为。在第二部分中,您将优化一个小的矩阵变换函数,目的是最小化缓存丢失的数量。

您将修改两个文件: csim.c和trans.c若要编译这些文件,请键入:
linux> make clean
linux> make
警告:不要让Windows WinZip程序打开你的程序。tar文件(许多Web浏览器被设置为自动执行此操作)。相反,请将文件保存到Linux目录中,并使用Linux tar程序来提取文件。一般来说,对于这个类,您不应该使用除Linux以外的任何平台来修改您的文件。这样做可能会导致数据丢失(以及重要的工作!)。

这个实验室有两个部分。在第A部分中,您将实现一个高速缓存模拟器。在B部分中,您将编写一个针对缓存性能进行优化的矩阵转置函数。
虚拟内存跟踪具有以下形式:
I 0400d7d4,8
M 0421c7f0,4
L 04f6b868,8
S 7ff0005c8,8
每一行表示一次或两个内存访问。每一行的格式为
操作字段类型,操作地址,字节大小
操作字段表示内存访问的类型:“I”表示指令加载,“L”表示数据加载,“S”表示数据存储,“M”表示数据修改(i。e., 数据加载后是数据存储)。在每个“I”之前从来没有一个空格。在M、L和S前面总是有一个空格。地址字段指定一个64位的十六进制内存地址。size字段指定该操作所访问的字节数。
官方给了一个命令可以尝试一下:

linux> valgrind --log-fd=1 --tool=lackey -v --trace-mem=yes ls -l

会输出一些类似的
CSAPP的Lab学习——CacheLab_第1张图片
可以解释一下,按照文档中介绍的那样:字母代表着不同的操作,后面是待操作地址,再后面是操作的字节数。


一、A部分:编写一个高速缓存模拟器

在A部分中,您将在csim.c中编写一个缓存模拟器,它以valgrind内存跟踪作为输入,模拟缓存内存的命中/未命中行为,并输出命中、未命中和删除的总数。

我们为您提供了一个引用缓存模拟器的二进制可执行文件,称为csim-ref,它模拟了在valgrind跟踪文件上具有任意大小和关联性的缓存的行为。在选择要驱逐的缓存行时,它使用LRU(最近最少的)替换策略。
参考模拟器采用以下命令行参数:

Usage: ./csim-ref [-hv] -s <s> -E <E> -b <b> -t <tracefile>-h:可选的帮助标志,打印使用信息
-v:可选的详细标志显示跟踪信息
-s<s>:设置索引位的数量(S = 2^s是集合的数量)
-E<E>:关联性(每组行数)
-b<b>:块位数(B = 2^b块大小)
-t<跟踪文件>:回溯的参数valgrind的名称

你在A部分的工作是填写csim.c文件,以便它接受相同的命令行参数,并生成与参考模拟器相同的输出。请注意,这个文件几乎完全为空。
你需要从头开始写它。

编程规则

  1. 在csim.c的标题注释中包含您的名称和loginID。
  2. 你的csim.c文件必须编译没有警告,以获得信用。
  3. 您的模拟器必须对任意的s、E和b正确工作。这意味着您将需要使用malloc函数为模拟器的数据结构分配存储空间。请输入“manmalloc”以获取有关此函数的信息。
  4. 对于这个实验室,我们只对数据缓存性能感兴趣,所以您的模拟器应该忽略所有的指令缓存访问(以“I”开头的行)。回想一下,valgrind总是把“I”放在第一列(前面没有空格),把“M”、“L”和“S”放在第二列(前面没有空格)。这可能会帮助您解析跟踪。
  5. 要获得A部分的积分,您必须在主函数结尾调用函数打印摘要,包括命中、漏掉和驱逐的总数:printSummary(hit_count, miss_count, eviction_count);
  6. 对于这个实验室,您应该假设内存访问被正确对齐,这样单个内存访问就不会跨越块边界。通过进行此假设,您可以忽略参数valgrind跟踪中的请求大小。

自我分析:麻烦参考426页(中文版)高速缓存的写法.
高速缓存结果可以用元组(S,E,B,m)来描述,其中 S=2^ s为组数,E为每个组的行数,B=2^b为块大小(字节),m=log2(M)为(主存)物理地址位数。CSAPP的Lab学习——CacheLab_第2张图片
我们为您提供了一个自动分级程序,称为测试-csim,它可以在引用跟踪上测试缓存模拟器的正确性。在运行测试之前,一定要编译你的模拟器:

linux> make
linux> ./test-csim

CSAPP的Lab学习——CacheLab_第3张图片
对于每个测试,它都显示您获得的点数、缓存参数、输入跟踪文件以及来自模拟器和参考模拟器的结果的比较。
以下是关于在A部分工作的一些提示和建议:

  1. 对小的跟踪进行初始调试,如traces/dave.trace。
  2. 参考模拟器采用一个可选的-v参数,支持详细输出,显示每次内存访问导致的命中、失败和删除。您不需要在csim.c代码中实现这个特性,但我们强烈建议您这样做。它将允许您直接在引用跟踪文件上比较模拟器和引用模拟器的行为,从而帮助您进行调试。
  3. 我们建议您使用getopt函数来解析命令行参数。您将需要以下标头文件:
#include 
#include 
#include 
  1. 每个数据加载(L)或存储(S)操作最多可能导致一次缓存丢失。数据修改操作(M)被视为一个负载,然后是一个存储到相同地址的存储。因此,M操作可能导致两次缓存命中,或一次未命中和一次命中加上可能的驱逐。
  2. 如果您想使用15-122年的 C0-style的合同,您可以包括contracts.h,我们已经在讲义目录中提供以方便您。

因为此处要用到getopt函数,getopt函数可以用来分析命令行参数,形式为:
int getopt(int argc,char * const argv[ ],const char * optstring);
具体可详见这位大佬的详解:
Linux下getopt()函数的简单使用

题中提到了LRU算法,这是一种缓存淘汰策略,具体详见LRU算法之我见。

主要流程是:

  1. 构造高速缓冲行结构
  2. 仿写主函数,使用getopt()函数
  3. 分配空间并释放
  4. 读取给的trace文件
  5. 模拟cache行为

构造高速缓冲行结构

首先仿照上面那张图来构造一个高速缓冲行结构,为结构体,其中有三个参数:有效位、标记位和高速缓冲块,其中高速缓冲块是以LRU替换策略使用的。

typedef struct {
    int vaild;
    int tag;
    int time; //此处应该是高速缓冲块,但是该题没有要求存储,且要求加入LRU替换算法,所以包含一个time表示访问时间间隔
} CacheLine, *CacheSet, **Cache;

仿写主函数,使用getopt()函数

定义getopt()函数和输出所需要的参数,根据题目提示,只需要s、E、b和t就可以:

int s, E, b, t, S; //参考模拟器参数
int hit_count, miss_count, eviction_count; //命中、漏掉和驱逐

然后根据getopt()函数的参考仿写:

int main(int argc, char* argv[]) //由于使用了getopt函数,需要传入
{
    /*仿写getopt函数的使用*/
    int ch;

    /*getopt()函数还有搜索值,不返回-1*/
    while ((ch = getopt(argc, argv, "s:E:b:t:")) != -1) {
        switch (ch) {
            case 's':
                s = atoi(optarg);
                S = (int)pow(2, s); //根据定义,S = 2^s
                break;
            case 'E':
                E = atoi(optarg);
                break;
            case 'b':
                b = atoi(optarg);
                break;
            case 't':
                t = atoi(optarg);
                strcpy(filePath, optarg);
                break;
            default:
                printf("ERROR!!!");
                break;
        }
    }

    mallocCache();
    readTraceFile();
    freeCache();

    printSummary(hit_count, miss_count, eviction_count);
    return 0;
}

分配空间并释放

其次根据题目中的要求(提示),我们需要使用malloc函数为模拟器的数据结构分配存储空间。也就是根据上一步中读取到的s, E, b使用malloc;函数来在堆上分配空间。
先定义:

Cache cache; //开辟空间

再写开辟缓存空间

/*动态分配缓存空间*/
void mallocCache() {
    if (s < 0) {
        printf("Not s!!!");
        exit(0);
    }

    cache = (Cache)malloc(S * sizeof(CacheSet));
    assert(cache);

    for (int i = 0; i < S; i++) {
        cache[i] = (CacheSet)malloc(E * sizeof(CacheLine));
        assert(cache[i]);
        memset(cache[i], 0, sizeof(CacheLine) * E); //初始化值,全置零
    }
}

最后是释放空间

/*释放空间*/
void freeCache() {
    for (int i = 0; i < S; ++i) {
        free(cache[i]);
    }
    free(cache);
}

读取给的trace文件

首先定义文件指针

char filePath[100]; //文件指针

再写读取文件的函数

/*读取给的trace文件*/
void readTraceFile() {
    FILE* file = fopen(filePath, "r");
    assert(file);

    /*检测文件是否存在*/
    if (file == NULL) {
        printf("NO File!!!");
        exit(0);
    }

    char type;//虚拟内存的类型
    uint64_t address;//虚拟内存访问地址
    int size; //虚拟操作访问的字节数

    /*只要还有值,就统统放入缓存中*/
    /*注意%c前面有个空格,因为只有I没有空格,但是I表示指令加载没啥用*/
    while (fscanf(file, " %c %lx,%d", &type, &address, &size) > 0) {
        /*按官方的解释,分为M、L、S分别讨论*/
        switch (type) {
            case 'M' :
                cacheOperation(address); //M是数据修改
            case 'L' : //L是数据加载,没啥用
            case 'S' :
                cacheOperation(address); //S是数据存储
                break;
        }
        lruUpdate();
    }
    fclose(file);
}

因为我们在其中使用了LRU算法,所以得更新下时间。

/*更新访问时间*/
void lruUpdate() {
    for (int i = 0; i < S; ++i) {
        for (int j = 0; j < E; ++j) {
            if (cache[i][j].vaild) {
                cache[i][j].time++;
            }
        }
    }
}

模拟cache行为

终于到了代码的最后部分,我们之前所写的代码都是为了这部分服务的,获取命中、漏掉和驱逐的值

代码:

/*模拟cache行为*/
void cacheOperation(uint64_t address) {

    uint64_t setIndex = ((1ULL << 63) - 1) >> (63 - s); //组索引位(这句我也解释的不好,就知道是跟计算机位数和最高位数有关)
    int tagIndex = address >> (b + s); //标记位(计算方法:物理地址address-(b+s))

    CacheSet cacheSet = cache[(address >> b) & setIndex];

    for (int i = 0; i < E; ++i) {
        /*看看匹没匹配,有效位是否存在和标记位是否相同*/
        if (cacheSet[i].vaild && cacheSet[i].tag == tagIndex) {
            hit_count++; //命中
            cacheSet[i].time = 0;
            return;
        }
    }

    miss_count++; //否则就是没命中

    /*取出块,并把这个块存储到组中,并返回*/
    /*存在空位,写入*/
    for (int i = 0; i < E; ++i) {
        if (!cacheSet[i].vaild) {
            cacheSet[i].vaild = 1; //有效位设置为1
            cacheSet[i].tag = tagIndex;
            cacheSet[i].time = 0;
            return;
        }
    }

    /*没有空位,使用LRU算法进行替换*/
    eviction_count++;
    int evictIndex = 0;
    int maxTime = 0;

    for (int i = 0; i < E; ++i) {
        if (cacheSet[i].time > maxTime) {
            maxTime = cacheSet[i].time;
            evictIndex = i;
        }
    }
    cacheSet[evictIndex].tag = tagIndex;
    cacheSet[evictIndex].time = 0;
}

注意,这些函数应重新排列顺序,我的顺序是:mallocCache、freeCache、lruUpdate、cacheOperation、readTraceFile和最后的主函数main,不然会报下面的错误。
CSAPP的Lab学习——CacheLab_第4张图片
先执行make,再./test-csim,成功实现!
CSAPP的Lab学习——CacheLab_第5张图片
到这里我们的A部分才算告一段落。ps:我的代码第一次编译通过了但是执行的答案不对,回头一查发现少了个冒号,当误我一个小时(终端下断点还是不太熟。。。)

二、B部分:优化矩阵转置

在B部分中,您将在trans.c中编写一个转置函数,从而导致尽可能少的缓存丢失。
设A表示一个矩阵,Aij表示第i行和第j列的分量。A的转置,表示AT,是一个矩阵,Aij=Aji。
为了帮助你开始,我们给了你一个trans.c中的转置函数的例子,它计算N×M矩阵a的转置,并将结果存储在M×N矩阵B中:

char trans_desc[] = "Simple row-wise scan transpose";
void trans(int M, int N, int A[N][M], int B[M][N])

这个例子的转置函数是正确的,但效率低,因为访问模式导致相对较多的缓存丢失。
您在B部分中的工作是编写一个类似的函数,称为transpose_submit,这将最小化在不同大小的矩阵中的缓存丢失的数量:

char transpose_submit_desc[] = "Transpose submission";
void transpose_submit(int M, int N, int A[N][M], int B[M][N]);

不要更改您的transpose_submit的描述字符串(“Transpose submission”)。自动评分器搜索这个字符串,以确定使用哪个转置函数来评估信用。

编程规则

  1. 在trans.c的标题注释中包含您的名称和loginID。
  2. 你的trans.c代码必须编译时没有警告才能获得信用。
  3. 每个转置函数最多允许定义12个int类型的局部变量。
  4. 不允许您通过使用任何长类型的变量或使用任何位技巧来存储一个变量的多个值来偏离前面的规则。
  5. 您的转置函数可能不使用递归。
  6. 如果您选择使用辅助函数,那么在辅助函数和顶级转置函数之间的堆栈上一次可能没有超过12个局部变量。例如,如果你的转置声明了8个变量,然后你调用一个使用4个变量的函数,它调用另一个使用2的函数,那么堆栈上将有14个变量,你将违反规则。
  7. 您的转置函数可能不能修改数组 A. 但是,您可以对数组B的内容做任何您想做的事情.
  8. 不允许您在代码中定义任何数组或使用malloc的任何变体。

自我分析:判分中分为了三种情况3232,6464,61*67三个不同大小的输出矩阵上的正确性和性能.

我们为您提供了一个自动分级程序,称为test-trans。c,它可以测试您在自动分级器上注册的每个转置函数的正确性和性能。
您可以在trans.c文件中注册多达100个版本的转置函数。每个转置版本都有以下形式:

/* Header comment */
char trans_simple_desc[] = "A simple transpose";
void trans_simple(int M, int N, int A[N][M], int B[M][N])
{
/* your transpose code here */
}

通过调用该表单,向自动分级器注册一个特定的转置函数:

registerTransFunction(trans_simple, trans_simple_desc);

在registerFunctions中,功能程序在 trans.c。在运行时,自动评分器将评估每个已注册的转置函数并打印结果。当然,其中一个注册函数必须是transpose_submit函数:

registerTransFunction(transpose_submit, transpose_submit_desc);

请参见默认的trans.c函数,以了解它是如何工作的示例。
自动分级器以矩阵大小作为输入。它使用valgrind来生成每个注册的转置函数的跟踪。然后,它通过在具有参数(s = 5,E = 1,b = 5)的缓存上运行参考模拟器来计算每个跟踪。
例如,要在32×32矩阵上测试您注册的转置函数,重新构建测试-转换,然后使用M和N的适当值运行它:

linux> make
linux> ./test-trans -M 32 -N 32
Step 1: Evaluating registered transpose funcs for correctness:
func 0 (Transpose submission): correctness: 1
func 1 (Simple row-wise scan transpose): correctness: 1
func 2 (column-wise scan transpose): correctness: 1
func 3 (using a zig-zag access pattern): correctness: 1
Step 2: Generating memory traces for registered transpose funcs.
Step 3: Evaluating performance of registered transpose funcs (s=5, E=1, b=5)
func 0 (Transpose submission): hits:1766, misses:287, evictions:255
func 1 (Simple row-wise scan transpose): hits:870, misses:1183, evictions:1151
func 2 (column-wise scan transpose): hits:870, misses:1183, evictions:1151
func 3 (using a zig-zag access pattern): hits:1076, misses:977, evictions:945
Summary for official submission (func 0): correctness=1 misses=287

在这个例子中,我们在trans.c中注册了四种不同的转置函数。test-trans测试每个注册的功能,显示每个功能的结果,并提取结果以供正式提交。以下是一些关于在B部分工作的提示和建议。

  1. test-trans程序在文件trace.fi中保存函数i的跟踪。这些跟踪文件是非常宝贵的调试工具,它可以帮助您准确地理解每个转置函数的命中和失败来自哪里。要调试一个特定的函数,只需通过详细的选项通过参考模拟器运行它的跟踪:
linux> ./csim-ref -v -s 5 -E 1 -b 5 -t trace.f0
S 68312c,1 miss
L 683140,8 miss
L 683124,4 hit
L 683120,4 hit
L 603124,4 miss eviction
S 6431a0,4 miss
...
  1. 由于转置函数是在直接映射的缓存上进行评估的,因此冲突丢失是一个潜在的问题。考虑代码中可能出现的冲突,特别是对角线。试着考虑能够减少这些冲突错过的访问模式。
  2. 阻塞是减少缓存丢失的一种有用技术。看我的理解
http://csapp.cs.cmu.edu/public/waside/waside-blocking.pdf

32 * 32矩阵转置

跟A部分一样,首先分析题目,提示给出了三点建议:自动分级器进行优化、对角线冲突问题和阻塞技术。
我们先来分析一下为什么会这么慢,首先看下初始转置操作函数的代码(也就是它起始代码)

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

我们可以大致分析出来为什么会这么高:其原因就是数组A与数组B的访问方式正好相反,当先按行优先顺序访问,因为题中给出的参数是(s = 5,E = 1,b = 5),则S=2^s=32、E=1、B=2 ^b=32,又因为int为4字节,所以一个高速缓冲块最多存8个int型,一共有32组。
继续分析:理论上A运行的时候并不会发生较多的miss,但是当你执行第二句的时候为列遍历B的一个元素,前8次还好说不中就不中可以存,但是当你访问到第9列的时候,此时cache已经存储了8(列)*4(int型大小)*32(每一列的行元素),意味着已经存满了,然后你为了存第9列的元素,就只能发生cache冲突,映射到同一缓冲组中,将第1列的行元素顶掉,等你访问1行2列的元素时又要重新加载。。。循环浪费。
CSAPP的Lab学习——CacheLab_第6张图片
毛用没有。
我们可以采用刚才介绍的分块思想,将每一个小块设为8的倍数,然后将这8个元素都进行操作再替换,这样可以极大的节省。
CSAPP的Lab学习——CacheLab_第7张图片
根据提示还有一个问题:对角线
因为对角线其实是根本不动的,属于原地tp,但是仍然会引发冲突(因为重复),所以也要进行单独处理。
我们可以引入局部变量,因为局部变量存储在寄存器中,不涉及内存访问。
transpose_submit函数整体代码如下:

void transpose_submit(int M, int N, int A[N][M], int B[M][N])
{
    int a, b, c, d, e, f, g, h;
    for (int i = 0; i < N; i += 8) {
        for (int j = 0; j < M; j += 8) {
            for (int ii = i; ii < i + 8; ++ii) {
                a = A[ii][j];
                b = A[ii][j + 1];
                c = A[ii][j + 2];
                d = A[ii][j + 3];
                e = A[ii][j + 4];
                f = A[ii][j + 5];
                g = A[ii][j + 6];
                h = A[ii][j + 7];

                B[j][ii] = a;
                B[j + 1][ii] = b;
                B[j + 2][ii] = c;
                B[j + 3][ii] = d;
                B[j + 4][ii] = e;
                B[j + 5][ii] = f;
                B[j + 6][ii] = g;
                B[j + 7][ii] = h;
            }
        }
    }
}

终端输入

linux> make
linux> ./test-trans -M 32 -N 32

得出答案,misses287,小于300,成功!
CSAPP的Lab学习——CacheLab_第8张图片
在网上看到了一位大佬的继续优化,我们都知道是因为B矩阵在列优先访问才导致了这个最大的问题,且题目中虽然规定不可以修改A矩阵的二维数组,但可以修改B矩阵的。那么我们可以在使用局部变量的方法的同时对B也进行行优先访问,之后再在B内部转置。(我其实也想到了这个招,就是懒了)
贴下大佬的题解,大佬写的巨详细,我这里看不懂的地方可以参考大佬的解释
代码:

void transpose_submit(int M, int N, int A[N][M], int B[M][N])
{
    const int len = 8;
    int a, b, c, d, e, f, g, h, k, s;
    for (int i = 0; i < N; i += len) {
        for (int j = 0; j < N; j += len) {
            // copy
            for (k = i, s = j; k < i + len; k++, s++) {
                a = A[k][j];
                b = A[k][j + 1];
                c = A[k][j + 2];
                d = A[k][j + 3];
                e = A[k][j + 4];
                f = A[k][j + 5];
                g = A[k][j + 6];
                h = A[k][j + 7];
                B[s][i] = a;
                B[s][i + 1] = b;
                B[s][i + 2] = c;
                B[s][i + 3] = d;
                B[s][i + 4] = e;
                B[s][i + 5] = f;
                B[s][i + 6] = g;
                B[s][i + 7] = h;
            }
            // transpose
            for (k = 0; k < len; k++) {
                for (s = k + 1; s < len; s++) {
                    a = B[k + j][s + i];
                    B[k + j][s + i] = B[s + j][k + i];
                    B[s + j][k + i] = a;
                }
            }
        }
    }
}

优化后:
CSAPP的Lab学习——CacheLab_第9张图片

64 * 64矩阵转置

一样的想法,但是由于是现在为64 * 64,一行元素是64个,以至于现在i行与i+4行就会发生冲突。如果还使用8 * 8矩阵分块,就会在内部发生冲突。理论上可以使用4 * 4矩阵分块,即

int a, b, c, d;
for (int i = 0; i < N; i += 4) {
    for (int j = 0; j < M; j += 4) {
        for (int ii = i; ii < i + 4; ++ii) {
            a = A[ii][j];
            b = A[ii][j + 1];
            c = A[ii][j + 2];
            d = A[ii][j + 3];

            B[j][ii] = a;
            B[j + 1][ii] = b;
            B[j + 2][ii] = c;
            B[j + 3][ii] = d;
        }
    }
}

我们发现效果其实并不理想
CSAPP的Lab学习——CacheLab_第10张图片
分析可知,一个cache块一组能存8个int,这么搞只能存一半。
我们可以想到,可以先进行8 * 8分块,然后再在这里进行4 * 4分块
逻辑图解:
比如下图这个8 * 8矩阵
CSAPP的Lab学习——CacheLab_第11张图片
我们可以进行如下变换,首先进行上四行变换,即
CSAPP的Lab学习——CacheLab_第12张图片

然后再对A进行逐列的进行后4行前四列的转置,即:CSAPP的Lab学习——CacheLab_第13张图片
重复这个操作,最后变成这个样子,即:
CSAPP的Lab学习——CacheLab_第14张图片
最后进行后面的处理,就不贴了直接转就行。
代码:

void transpose_submit(int M, int N, int A[N][M], int B[M][N])
{
    int a, b, c, d, e, f, g, h, i, j, k, l;
    for (i = 0; i < N; i += 8)
    {
        for (j = 0; j < M; j += 8) {
            for (k = i; k < i + 4; k++) {
                a = A[k][j];
                b = A[k][j + 1];
                c = A[k][j + 2];
                d = A[k][j + 3];
                e = A[k][j + 4];
                f = A[k][j + 5];
                g = A[k][j + 6];
                h = A[k][j + 7];

                B[j][k] = a;
                B[j + 1][k] = b;
                B[j + 2][k] = c;
                B[j + 3][k] = d;
                B[j][k + 4] = e;
                B[j + 1][k + 4] = f;
                B[j + 2][k + 4] = g;
                B[j + 3][k + 4] = h;
            }
            for (l = j; l < j + 4; l++) {
                a = A[i + 4][l];
                b = A[i + 5][l];
                c = A[i + 6][l];
                d = A[i + 7][l];
                e = B[l][i + 4];
                f = B[l][i + 5];
                g = B[l][i + 6];
                h = B[l][i + 7];

                B[l][i + 4] = a;
                B[l][i + 5] = b;
                B[l][i + 6] = c;
                B[l][i + 7] = d;
                B[l + 4][i] = e;
                B[l + 4][i + 1] = f;
                B[l + 4][i + 2] = g;
                B[l + 4][i + 3] = h;
            }
            for (k = i + 4; k < i + 8; k++) {
                a = A[k][j + 4];
                b = A[k][j + 5];
                c = A[k][j + 6];
                d = A[k][j + 7];
                B[j + 4][k] = a;
                B[j + 5][k] = b;
                B[j + 6][k] = c;
                B[j + 7][k] = d;
            }
        }
    }
}

得出答案,misses1179,小于1300,成功!
CSAPP的Lab学习——CacheLab_第15张图片

61 * 67矩阵转置

这题说难也难,说简单也是真的简单(因为可以试出来),比较玄学。。。
因为没办法对齐处理,基本只能靠猜和一个一个试。
最后发现17 * 17的时候数据最小,为1950。
此处引用一位博主的尝试过程,这位博主我也不知道是谁,但是菜鸡在此感谢这位大佬!
ps:你们是逃学威龙,是正义使者,是这帮实验室的终极克星,照亮了像我这样的萌新前进的道路!
CSAPP的Lab学习——CacheLab_第16张图片
代码:

void transpose_submit(int M, int N, int A[N][M], int B[M][N])
{
    int i, j, h, k;
    for (i = 0; i < N; i += 17)
    {
        for (j = 0; j < M; j += 17)
        {
            for (h = i; h < i + 17 && h < N; h++)
            {
                for (k = j; k < M && k < j + 17; k++)
                {
                    B[k][h] = A[h][k];
                }
            }
        }
    }
}

得出答案,misses1950,小于2000,成功!
CSAPP的Lab学习——CacheLab_第17张图片


总结

这个Lab真的做的爽!(虽然被虐的过程很惨)。让我对cache的过程更加清楚与明白,之前看书的时候还不屑一顾就这,一看就懂,结果一做就废,翻来覆去的去扣每一个知识点的定义。B部分的优化更加具体且贴近实际情况,有的时候可能只是小小的改变就能让整个代码运行的的更加流畅。真的是很爽的一个Lab。

你可能感兴趣的:(csapp的Lab学习,学习,程序人生,linux,c++)