本文主要是介绍《深入理解计算机系统》实验五Cache Lab下载和官方文档机翻,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
前言
《深入理解计算机系统》官网:http://csapp.cs.cmu.edu/3e/labs.html
该篇文章是
实验五Cache Lab的Writeup(cachelab.pdf)机翻
原文:http://csapp.cs.cmu.edu/3e/cachelab.pdf
和
cachelab.pdf中提到的分块技术官方文档机翻
原文http://csapp.cs.cmu.edu/public/waside/waside-blocking.pdf
阅读文档能对实验有所帮助。
在官网点击下方即可下载实验五的文件
cachelab.pdf
1.组织工作
这是一个个人项目。您必须在64位x86-64机器上运行此实验室。
2.概述
这个实验将帮助您理解缓存内存对C程序性能的影响。
实验室由两部分组成。在第一部分中,您将编写一个模拟高速缓存行为的小型C程序(大约200-300行)。在第二部分中,您将优化一个小的矩阵转置函数,目标是最小化缓存未命中的数量。
3.下载作业
这将创建一个名为cachelab-handout的目录,其中包含许多文件。您将修改两个文件:csim.c和trans.c。要编译这些文件,输入:
警告:不要让Windows WinZip程序打开您的.tar文件(许多Web浏览器都设置为自动打开)。相反,将文件保存到您的Linux目录,并使用Linux tar程序来解压缩文件。一般来说,对于这个类,您应该永远不要使用Linux以外的任何平台来修改您的文件。这样做可能会导致数据丢失(以及重要的工作!)
4.描述
实验室由两部分组成。在第A部分中,您将实现一个缓存模拟器。在第B部分中,您将编写一个矩阵转置函数,该函数针对缓存性能进行了优化。
4.1 参考跟踪文件
讲义目录的子目录traces包含一个参考跟踪文件集合,我们将使用这些文件来评估您在第a部分中编写的缓存模拟器的正确性。跟踪文件是由一个名为valgrind的Linux程序生成的。例如,输入
在命令行上运行可执行程序“ls -l”,捕获其每次内存访问的跟踪,并在stdout上打印它们。
Valgrind内存跟踪具有以下形式:
每一行表示一个或两个内存访问。每一行的格式为
operation字段表示内存访问的类型:“I”表示指令加载,“L”表示数据加载,“S”表示数据存储,“M”表示数据修改(即,数据加载后跟着一个数据存储)。在每个“I”之前从来没有空格。每个“M”、“L”和“S”前面总是有一个空格。address字段指定一个64位的十六进制内存地址。size字段指定操作访问的字节数。
4.2 A部分:编写缓存模拟器
在A部分中,您将在csim中编写一个缓存模拟器。以valgrind内存跟踪作为输入,模拟该跟踪上缓存的命中/未命中行为,并输出命中、未命中和逐出的总数。
我们为您提供了一个名为csim-ref的参考缓存模拟器的二进制可执行文件,该模拟器模拟valgrind跟踪文件上具有任意大小和关联性的缓存的行为。在选择要逐出的缓存线时,它使用LRU(最近使用最少的)替换策略。参考模拟器采用以下命令行参数:
- -h:打印使用信息的可选帮助标志
- -v:可选的verbose标志,用于显示跟踪信息
- -s <s>:集合索引位的数量(s = 2s是集合的数量)
- -E <E>:结合性(每个集合的行数)
- -b <b>:块位数(B = 2b是块大小)
- -t <tracefile>:要重播的valgrind跟踪的名称
命令行参数基于CS:APP2e教科书第597页中的符号(s、E和b)(译者注:应该对应的是《CS:APP3e》的P426-427)。例如
详细模式下的相同示例:
A部分的工作是填写csim.c文件,以便它采用相同的命令行参数并生成与参考模拟器相同的输出。请注意,此文件几乎完全为空。你需要从头开始写。
A部分的编程规则
- 在csim.c的头注释中包含您的姓名和loginID。
- 你的csim.c文件必须在没有警告的情况下编译才能获得分数(credit)。
- 你的模拟器必须对任意的s, E和b正常工作。这意味着你需要使用malloc函数为你的模拟器的数据结构分配存储空间。输入“man malloc”获取关于此功能的信息。
- 对于这个实验,我们只对数据缓存性能感兴趣,所以你的模拟器应该忽略所有的指令缓存访问(以“I”开头的行)。回想一下,valgrind总是把“I”放在第一列(前面没有空格),而把“M”、“L”和“S”放在第二列(前面有空格)。这可能帮助您解析跟踪。
- 要获得A部分的积分,必须在主函数末尾调用函数printSummary,其中包含命中、未命中和逐出的总数:
- 对于这个实验,您应该假设内存访问是正确对齐的,这样单个内存访问永远不会跨越块边界。通过这样的假设,您可以忽略valgrind跟踪中的请求大小。
4.3 第二部分:优化矩阵转置
在B部分,你将在trans.c中写一个转置函数,以尽可能减少缓存遗漏。
设A表示一个矩阵,Aij表示第i行和第j列上的分量。A的转置,记作AT,是一个Aij = AijT的矩阵
为了帮助你入门,我们在trans.c中给出了一个转置函数的例子,它计算N × M矩阵A的转置,并将结果存储在M × N矩阵B中:
示例的转置函数是正确的,但它的效率很低,因为访问模式会导致相对较多的缓存丢失。
在B部分中,您的工作是编写一个类似的函数,称为transpose_submit,它在不同大小的矩阵中最小化缓存遗漏的数量
请勿更改转置提交函数的描述字符串(“Transpose submission”)。自动加载器搜索此字符串以确定要计算分数(credit)的转置函数。
B部分的编程规则
- 在trans.c的头注释中包含您的姓名和loginID。
- 您在trans.c中的代码必须在没有警告的情况下编译才能获得分数(credit)。
- 每个转置函数最多允许定义12个int类型的局部变量(注1:这一限制的原因是我们的测试代码不能计数对堆栈的引用。我们希望您限制对堆栈的引用,并将重点放在源数组和目标数组的访问模式上。)
- 不允许通过使用任何long类型的变量或使用任何位技巧将多个值存储到单个变量来绕过前面的规则。
- 转置函数不能用递归。
- 如果您选择使用辅助函数,那么在堆栈上的辅助函数和顶级转置函数之间的局部变量一次不能超过12个。例如,如果转置声明了8个变量,然后调用了一个使用4个变量的函数,然后调用了另一个使用2个变量的函数,那么堆栈上就会有14个变量,这就违反了规则。
- 转置函数不能修改数组A。但是,可以对数组B的内容执行任何操作。
- 不允许在代码中定义任何数组,也不允许使用malloc的任何变体。
5.评价
本节描述如何评估你的工作。本次实验的满分为60分:
- A部分:27分
- B部分:26分
- 风格:7分
5.1 A部分评价
对于第A部分,我们将使用不同的缓存参数和跟踪来运行您的缓存模拟器。有8个测试用例,每个值3分,除了最后一个,它值6分:
您可以使用参考模拟器csim-ref获得每个测试用例的正确答案。在调试过程中,使用-v选项可以详细记录每个命中和未命中。
对于每个测试用例,输出正确数量的缓存命中、未命中和逐出将为您提供该测试用例的全部积分。您报告的每个命中、未命中和逐出次数都相当于该测试用例分数(credit)的1/3。也就是说,如果一个特定的测试用例值3分,并且您的模拟器输出了正确的命中和未命中次数,但报告了错误的驱逐次数,那么您将获得2分。
5.2 B部分评价
对于B部分,我们将在三个不同大小的输出矩阵上评估您的transpose_submit函数的正确性和性能:
- 32 × 32 (M = 32, N = 32)
- 64 × 64 (M = 64, N = 64)
- 61 × 67 (M = 61, N = 67)
5.2.1 性能(26分)
对于每个矩阵大小,使用valgrind提取函数的地址跟踪,然后使用参考模拟器在带有参数(s=5,E=1,b=5)的缓存上重放该跟踪,从而评估transpose_submit函数的性能。
每个矩阵大小的性能分数与未命中数m成线性比例,达到某个阈值
- 32 × 32: 如果 m < 300 8 分, 如果 m > 600 0 分
- 64 × 64: 如果 m < 1,300 8 分, 如果 m > 2000 0 分
- 61 × 67: 如果 m < 2,000 8 分, 如果 m > 3000 0 分
您的代码必须正确,才能接收特定大小的任何性能点。您的代码只需要对这三种情况正确,您可以针对这三种情况专门优化它。特别是,让函数显式地检查输入大小并实现针对每种情况优化的单独代码是完全没问题的。
5.3 风格评价
关于编码风格有7点。这些将由课程工作人员手动分配。风格指南可以在课程网站上找到。
课程人员将检查B部分的代码,看是否存在非法数组和过多的局部变量。
6.在实验室工作
6.1 A部分的工作
我们已经为您提供了一个自动评分程序,叫做test-csim,用来测试您的评分是否正确
对于每个测试,它显示您获得的点数、缓存参数、输入跟踪文件,以及来自您的模拟器和参考模拟器的结果的比较。
这里有一些关于A部分的提示和建议:
- 在小型跟踪(如traces/dave.trace)上进行初始调试。
- 参考模拟器接受一个可选的-v参数,该参数允许详细输出,显示每次内存访问所产生的命中、未命中和驱逐。您不需要在csim.c代码中实现这个特性,但我们强烈建议您这样做。它将帮助您调试,允许您直接比较您的模拟器的行为与参考模拟器上的参考跟踪文件。
- 我们建议您使用getopt函数来解析命令行参数。你需要以下头文件:
详见“man 3 getopt”。 - 每次数据加载(L)或存储(S)操作最多只能导致一个缓存未完成。数据修改操作(M)被视为加载后跟随到相同地址的存储。因此,一个M操作可能导致两次缓存命中,或者一次未命中,一次命中,加上一次可能的驱逐。
- 如果你想使用15-122年的c0-style的合同,你可以包括contracts.h,为了方便你,我们在讲义目录中提供了这个。
6.2 B部分的工作
我们已经为您提供了一个自动评分程序,称为test-trans.c,它测试您在自动评分程序中注册的每个转置函数的正确性和性能。
您可以在trans.c文件中注册多达100个转置函数版本。每个转置版本有以下形式:
通过调用表单,向自动加载器注册特定的转置函数
在trans中的registerFunctions例程中。c、 在运行时,自动加载器将评估每个注册转置函数并打印结果。当然,其中一个注册函数必须是您正在提交以获得分数(credit)的transpose_submit函数:
请参阅缺省的trans.c函数,以获得它如何工作的示例。
自动分级器将矩阵大小作为输入。它使用valgrind生成每个注册转置函数的跟踪。然后,它通过在具有参数(s = 5, E = 1, b = 5)的缓存上运行参考模拟器来计算每个跟踪。
例如,要在32×32矩阵上测试注册的转置函数,请重新生成测试转置,例如,要测试注册的转置函数
在这个例子中,我们在trans.c中注册了四个不同的转置函数。test-trans程序测试每个注册函数,显示每个函数的结果,并为正式提交提取结果。
这里有一些关于B部分工作的提示和建议。
- test-trans程序将函数i的跟踪保存在trace.fi(注2:因为valgrind引入了许多与代码无关的堆栈访问,所以我们从跟踪中过滤掉了所有的堆栈访问。这就是为什么我们禁止了局部数组并限制了局部变量的数量。)文件中。这些跟踪文件是无价的调试工具,可以帮助您准确地理解每个转置函数的成功和失败来自何处。要调试一个特定的函数,只需通过带有verbose选项的引用模拟器运行它的跟踪:
- 由于转置函数是在直接映射缓存上评估的,因此冲突未命中是一个潜在问题。考虑代码中可能出现的冲突遗漏,尤其是在对角线上。试着想想可以减少这些冲突未命中次数的访问模式。
- 分块是一种减少缓存遗漏的有用技术。看(译者注:下面有该篇文档的机翻):
有更多的信息。
6.3 把他们放在一起
我们为您提供了一个名为./driver.py的驱动程序,它执行对模拟器和转置代码的完整评估。这是您的讲师用来评估您的交卷的相同程序。驱动程序使用test csim评估模拟器,并使用test trans评估三种矩阵大小上提交的转置函数。然后,它会打印您的成绩和您获得的分数的摘要。
要运行驱动程序,输入:
7.上交作业
每次在cachelab-handout目录中键入make时,Makefile都会创建一个名为userid-handin.tar的tar文件,其中包含当前的csim.c和trans.c文件。
waside-blocking.pdf
警告
本文档中的材料是Randal E. Bryant和David R. O 'Hallaron合著的《计算机系统,程序员的视角,第二版》的补充材料,由prence - hall出版,版权在2011年。在本文档中,所有以“CS:APP2e”开头的参考资料均为本书。有关本书的更多信息,请访问csapp.cs.cmu.edu。
本文件受版权保护,现向公众开放。您可以自由地复制和分发它,但是您不应该使用任何没有归属的材料。
1.介绍
有一种有趣的技术叫做分块,它可以改善内部循环的时间局部性。分块的一般思想是将程序中的数据结构组织成称为块的大块。(在这种情况下,“块”指的是应用程序级的数据块,而不是缓存块。)程序的结构是这样的:它将一个数据块加载到L1缓存中,对该数据块执行所有需要的读写操作,然后丢弃该数据块,加载下一个数据块,以此类推。
与用于改善空间局域性的简单循环转换不同,分块使代码更难阅读和理解。因此,它最适合于优化编译器或频繁执行的库例程。尽管如此,研究和理解这项技术还是很有趣的,因为它是一个通用的概念,可以在某些系统上产生很大的性能增益。
2.矩阵乘法的分块版本
分块矩阵乘法程序的工作原理是把矩阵分成子矩阵,然后利用数学事实,这些子矩阵可以像标量一样操作。例如,假设我们要计算C = AB,其中A、B和C都是8 × 8矩阵。然后我们可以将每个矩阵分成4个4 × 4的子矩阵:
在哪里
图1显示了分块矩阵乘法的一个版本,我们称之为bijk版本。这段代码背后的基本思想是将A和C划分为1 × bsize的行条,并将B划分为bsize × bsize的块。最内层的(j, k)循环对将a的一个切片乘以B的一个块,并将结果累积为C的一个切片。i循环遍历a和C的n个行切片,在B中使用相同的块。
图2给出了图1中的分块代码的图形化解释。它的关键思想是将B块加载到缓存中,用完它,然后丢弃它。对A的引用具有良好的空间局部性,因为访问每个片段的步幅为1。也有很好的时间局部性,因为整个银条是连续引用bsize时间。对B的引用具有良好的时间局域性,因为整个bsize × bsize块连续被访问n次。最后,对C的引用具有良好的空间局部性,因为银条中的每个元素都是连续写入的。注意,对C的引用没有良好的时间局部性,因为每个片段只被访问一次。
分块会使代码难以阅读,但它也会带来巨大的性能收益。图3显示了两个版本的分块矩阵乘法在Pentium III Xeon系统(bsize = 25)上的性能。注意,与最好的非分块版本相比,分块将运行时间提高了两倍,从每次迭代的20个周期减少到大约10个周期。关于分块的另一件有趣的事情是,每次迭代的时间几乎保持不变,随着数组大小的增加。对于较小的数组大小,分块版本中的额外开销导致其运行速度比非分块版本慢。在大约n = 100处有一个交叉点,在这个交叉点之后,分块的版本运行得更快。
我们要注意的是,分块矩阵乘法并不能提高所有系统的性能。例如,在Core i7系统上,存在矩阵乘法的非分块版本,它与最好的分块版本具有相同的性能。
这篇关于《深入理解计算机系统》实验五Cache Lab下载和官方文档机翻的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!