存储器层次结构实验报告
一.存储器山
1. 实验要求和目的
实验要求:在自己的电脑上实验存储器山的实验,并绘制如同课本445页6-41图片的三维图。
实验目的:测试自己电脑CPU的性能,并绘制出存储器山的图片,能够根据实验结果进行分析总结。
2. 实验测试条件说明
实验是在本人自己电脑上进行的测试,笔记本的相关信息如下:
型号: 宏碁 Acer Aspire VX5-591G
内存大小: 8G
CPU: Intel® Core™ i5-7300HQ CPU @2.50GHz 4核
高速缓存信息: L1 缓存:256KB
L2 缓存:1.0MB
L3 缓存:6.0MB
操作系统: Elementary OS
由于该实验的测试代码需要使用到fcyc2等头文件,这些文件内部调用了只能在Linux系统下运行的相关函数,于是该试验测试数据时我们是在Linux系统下进行的。
3. 实验过程
因为实验是在Linux系统下运行的,所以直接采用的命令行编译了程序,然后将结果输出到一个txt文档中,最后直接使用MATLAB读取txt文档中的数据进行画图。整个过程由于没有什么特别需要讲解的,故在此不多叙述。
4. 实验结果
实验结果是分为两部分,一部分是输出的txt文档,一部分是绘制的三维图,结果如下:
实验数据大小从1KB到128MB,步长从s1到s31。可能由于格式问题数据比较杂乱,下面是根据数据绘制出的存储器山图片:
5. 实验分析
首先我们来观察得到的实验数据,可以看到当步长相同时,size大小从1KB到128MB变化得到的吞吐量有一个先增大再减小的过程,其中最大值出现在接近一级缓存256KB处。而当size大小固定时,步长越长,得到的吞吐量是依次减少的。
在这里我们可以探讨两个问题:第一个是为什么数据是呈现这样的分布态势,其中有什么原因?第二个是我们能够从这个实验中学习到什么东西?
首先关于数据分布态势,也分为两个部分。首先,为什么仅仅观察size大小时,吞吐量最大值会出现在接近一级缓存的256KB处(实际应该更接近128KB,因为一级缓存的数据部分和指令部分是同样的大小,理论上应该更接近数据部分的128KB)?这个其实很好解释,从我们课上学过的存储器结构我们可以得知从寄存器,L1到最后主存,本地磁盘访问的速度是越来越慢的。而吞吐量计算公式中表明,访问的时间周期越短,保持size/stride不变时,吞吐量会越大,所以由于三级缓存和主存访问的时间周期长短不同,我们可以得知一级缓存吞吐量是大于二级缓存的,而二级缓存是大于三级缓存的。那为什么是在接近一级缓存大小时达到最大值呢?这也和我们学习的缓存命中等有关。如果工作集的大小越接近一级缓存,说明此时其缓存命中率就越高,这个应该很好理解。工作集大小很小时,往往需要访问的数据在一级缓存中找不到,还需要在下一级的缓存中查找,这个过程也是很耗费时间的。所以工作集大小越接近一级缓存,命中率就越高,这样也就减少访问时间周期,从而也能解释为何吞吐量在size达到一级缓存大小时达到最大值了。
然后当工作集大小不变,改变步长时,吞吐量是随着步长的增加稳步下降的。举个例子,就是当L1中的读不命中会导致L2中的一个块传到L1中,后面在L1的这个块上面也会有一定量的命中。而此时,随着步长的增加,L1中命中与不不命中的比值也增加了,这也导致了吞吐量的降低。故可知随着步长的逐步增加,吞吐量会下降。
最后就是我们做这个实验是为了什么?想要学习到什么东西?从课本中我们也了解到了存储器山它就是一座时间局部性与空间局部性的山,从不同的角度去进行分析,我们可以得到相应的结果。我们作为程序员,在了解了存储器山之后,我们也应该也需要学会尽量去利用时间局部性和空间局部性原理,去让我们的程序运行在“山峰”而不是“谷底”。利用课本上的一句话进行总结就是:利用时间局部性,使得频繁使用的字从L1中取出,还要利用空间局部性,使得尽可能多的字从一个L1高速缓存中访问到。
6. 实验感想和总结
这个实验由于测试代码在课本上已经给出,其中缺少的fcyc2等文件也可以在GitHub等网站上找到,所以在代码方面没有难度,只需要正常的编译运行即可。最后的结果也可以由MATLAB直接导入,同时在GitHub上也有相应的画出存储器山的代码,画图的过程也没有什么问题。
不过实验过程中也有一些小插曲,就是关于fcyc2文件。我们在GitHub上找到了很多不同版本的fcyc2文件,它们的测试代码都是一模一样的,但是有的版本测出来的吞吐量及其少,只有几百mb/s。但是这肯定与课本上的结果不符,因为虽然我个人电脑的CPU是i5的,课本上测试的是i7的,但是我的CPU的一级缓存和二级缓存都比它高,没有理由会比课本上少。我们也检查了fcyc2文件,但是我们也看不出什么端倪。后来换了一个版本的才恢复正常,有了上面的那幅图。
总的来说,这个实验没有特别大的难度,也让我对时间局部性和空间局部性也有了更深的一点了解。至于关于fcyc2的那个问题,之后如果有时间,可以更加深入的进行了解。
1. 实验要求和目的
实验要求:图像增强操作中有一种衡量标准为均方差(MSE),均方差计算的步长为1。此实验需要计算图像大小从512到4096时,空间局部性对均方差计算时间的影响。
实验目的:理解空间局部性,通过图像说明空间局部性对程序运行时间的影响。
2. 实验测试条件说明
实验是在本人自己电脑上进行的测试,笔记本的相关信息如下:
型号: 宏碁 Acer Aspire VX5-591G
内存大小: 8G
CPU: Intel® Core™ i5-7300HQ CPU @2.50GHz 4核
高速缓存信息: L1 缓存:256KB
L2 缓存:1.0MB
L3 缓存:6.0MB
操作系统: Windows10 家庭版
3. 实验过程
此题的要求虽然是对于图像进行增强处理,但是实际上我们就可以把图像看成是一个二维数组,如果是灰度图,则二维数组里存的就是数值,如果是RGB图,则二维数组中存放的是一个结构体,结构体中包含R,G,B三个数值。所以此题我们可以把其等价与有两个二维数组,然后将两个二维数组代入到MSE的公式中,求出运算的时间。
弄清楚这点后,代码就可以很清楚的写出来了,还有一点需要说明就是我们在计算时间时采用的是将程序运行10遍之后求平均时间这种方法,这样可以避免一定的偶然性。
最后运行代码,显示最后运行的时间结果即可。
4. 实验结果
从上面的结果可以看出基本上随着块大小以×2的速度增长时,花费的时间以近似2的平方的速率来增长。而我们从图像也可以看出这个结果。(图像的结果是用另一个结果,1.4ms,5.8ms,24.1ms,96.6ms,可以得出相同的结果)
5. 实验分析
该试验中由于只测试了从512到4096的数据,由于数组大小为512乘512时,花费的时间已经很短了,再减小数组大小测试也没有什么意义。从数据中,我们可以看出,基本上数组大小以乘2的速度增长时(实际上对于二维数组,数组大小变成了原来的4倍),花费的时间也是上一个花费时间的4倍左右,基本上处于一个线性增长的过程。
从时间结果中我主要有一些看法。第一点是关于程序运行时间随着数组大小线性增长。这说明我们是按照同样的顺序去访问的数组,然后进行运算,在这点上程序的空间局部性的特点都是相同的。当然这需要是在同一级的存储器当中。这些可以说明该程序在空间局部性上是做的比较好的。第二点的看法就是,其实从该题的实验目的中我有一点点疑惑,不知道是不是我对题目的理解有误。因为从无论是从实验的代码编写和实验的结果都可以看出,此题的实验结果并不能特别的看出空间局部性对于程序执行时间的影响。
空间局部性一般是指如果程序访问某个存储器地址后,又在较短时间内访问临近的存储器地址,则程序具有良好的空间局部性。然而在此实验中,我们对于数组的访问方式并没有改变,仅仅只是改变数组的大小而已。所以影响程序运行时间很大程度上是取决于数组大小的,即时间局部性,而不是空间局部性。
如果需要考察空间局部性对程序运行时间的影响,我们只需要在MSE函数中调换循环的顺序再运行即可。我也对此进行一下测试,测试的结果如下图:
从此图中可以看出,按列优先去访问数组时,数组大小从512*512到4096*4096时,程序执行的时间分别是:2.2ms,12.7ms,54.4ms,293.2ms。而按行优先访问时,时间分别为1.6ms,8.2ms,34.8ms,149.4ms。明显从数据中可以看出按列优先访问时间比按行优先访问的时间要长得多。而数组存储是按行优先存储的,所以从两组数据中对比可以看出空间局部性对程序运行时间的影响。
6. 实验感想和总结
此次实验我以为是需要真的去找一些图片去实际进行测试,而当王建荣老师无意间看到我们做实验后直接告诉我们就可以把图像直接当成一个二维数组去看待我才想到这样的方法。但是也正因为这样的方法,导致了后面的问题,就是空间局部性对程序的执行时间没有很大影响。
关于这个问题,后面是和同学讨论的时候察觉到可以自己将循环的顺序进行改变,这样也能够看出空间局部性对程序执行时间的影响。所以改变代码后也得到了新的数据进行了对比,得出了正确的结论。这也让我明白,有的时候一些看似复杂,摸不清头绪的问题,换个思路可以很简单的将之解决。
三.矩阵分块相乘
1. 实验要求和目的
实验要求:在上课时的课件最后,介绍了分块矩阵完成矩阵相乘。请实际编写代码,用实验验证,并分析分块大小,矩阵大小等对性能的影响,最后需要绘制出结果的图形。
实验目的:验证矩阵分块相乘在性能上优于普通的矩阵相乘,并分析相关原因。
2. 实验测试条件说明
实验是在本人自己电脑上进行的测试,笔记本的相关信息如下:
型号: 宏碁 Acer Aspire VX5-591G
内存大小: 8G
CPU: Intel® Core™ i5-7300HQ CPU @2.50GHz 4核
高速缓存信息: L1 缓存:256KB
L2 缓存:1.0MB
L3 缓存:6.0MB
操作系统: Windows10 家庭版
3. 实验过程
此实验主要是探究分块方法对于矩阵乘法效率的提升,然而对于一些比较小的矩阵,分块与不分块我们通过最后的数据并不能明显的得到结果,所以我们此次实验选用的矩阵大小是从512*512开始,每次SIZE*2,而块从8*8开始一直到256*256为止。然后我们针对矩阵大小为512*512和1024*1024的两种矩阵也做了对比分析(即也运行了未进行分块时的结果)。整个实验过程只要弄清楚了需要做什么,代码是比较好写的了。所以整个程序我们直接运行所写的代码,即可得到最后想要的结果,然后用MATLAB画出对应的图片即可。
4. 实验结果
实验结果主要是分为两个部分,一个是未分块时矩阵相乘的结果,一个是分块时矩阵相乘的结果。由于未分块时对于大小为2048*2048及以上的矩阵花费的时间太长,由于时间原因,对于这些数据也没有进行测试。故在MATLAB绘制图形时,也没有绘制未分块时的结果。具体的结果如下:
未分块矩阵:
SIZE=512 时,花费时间为1282.7ms
SIZE=1024时,花费时间为32473.5ms
分块矩阵:
具体结果用表格方式展示(单位为ms,Debug模式下)
8 |
16 |
32 |
64 |
128 |
256 |
|
512 |
826.8 |
859.5 |
801.1 |
765.8 |
1029.5 |
1038.4 |
1024 |
7765.2 |
7639.9 |
7229.5 |
9627.4 |
9669.4 |
10080.8 |
2048 |
62487.3 |
62319.2 |
61108.5 |
60891.3 |
77195.3 |
78500.7 |
4096 |
522227 |
570510 |
|
|
|
|
(图形由于数量级差距较大,图像可能不能直观的反映出结果,结合数据看就好)
5. 实验分析
由于在这个实验过程中遇到的问题较多,所以在此想分为几个方面来进行分析和讨论。
(1)未分块相乘与分块相乘之间效率的对比
其实这点是很显而易见的,通过上面的数据就可得知,在矩阵大小为512*512时,未分块的时间为1282.7ms,而分块的时间最长的也只需要1038.4ms。同理对于矩阵大小为1024*1024时也是一样,未分块是32473.5ms,而分块时间最长也只需要10080.8ms。从实验数据可以明显看出,分块矩阵相乘可以明显的提高矩阵相乘的效率。
至于为什么能够提高效率,这个原因其实很简单。当数组大小逐渐增大时,高速缓存的不命中率会很明显的增大,数组越大越明显。而对矩阵进行分块后,明显的降低了不命中率,这样的话计算起来花费的时候会明显缩短。因为主要是从后一级的缓存或者主存中调数据到前面的缓存这个过程是很耗费时间的。而分块相乘能明显的减少这个过程,所以在以后矩阵相乘时,面对大小较大的矩阵时可以采用分块相乘的方法。
(2)选取的块大小对于程序执行时间的影响
探究选取的块大小对程序运行时间的影响,我们就先固定矩阵大小不变。不过块大小等于矩阵大小的话也没有意义了,所以统一的设为8到256即可。我们从数据和图形中可以看出。对于矩阵大小512*512和2048*2048时,块大小从8到64增长时,花费的时间是逐渐减少的,而从64到128时会有一个跳跃性的增加。从这些数据中我们可以分析出大概在块大小等于64(64*64)时,花费的时间最短。不过对于矩阵1024*1024是个例外,我们在后面再来说明这个问题。
在上课时,在PPT上面,我们也可以看到结果,对于矩阵分块乘法,块大小越大越好,不过需要有3B*B<C的限制。C是Cache size,即缓存大小,前面系数为3是因为在代码中我们有两个相乘的矩阵,还有第三个矩阵用来存储他们相乘得到的结果。对于我的电脑,我的一级缓存L1大小为256KB(数据缓存部分为128KB),而当块大小为64*64时,得到的结果是64*64*8*3=96KB,是满足条件的。而块大小变成128*128时,得到的结果为128*128*8*3=384KB,这样就大于一级缓存的大小,此时就会发生不命中的现象,从而会导致运行时间有一个跳跃性的增长。所以在选取块大小时,就在3B*B<C的限制下选取最大的块大小即可。
关于矩阵大小为1024*1024,花费时间发生跳跃性增长是在块大小从32变到64时发生的。本来之前也是对这个问题有一些疑惑,但是在写报告后与同学进行了讨论,对程序重新进行编译运行,只是这次并没有采用Debug方式,而是采用了Release方式进行运行,得到了不太一样的结果。结果以表格的形式展示如下面的表格和图像(由于对于release模式时间普遍都有减少,所以在此运行了矩阵大小为4096*4096时的各项数据的结果)。
从结果中可以看出,在release模式下进行运行时,时间缩短了很久。这是因为Debug模式下它包含很多调试信息,并且对程序运行的速度没有做过多的优化。而相比Debug模式,Release它在速度上进行了优化,但是它没有对源代码进行调试,这主要是两者的差异。在其它实验中我都是采用Debug模式,但是由于此实验的特殊性,在此也用Release模式运行一次,但是不管用哪个模式,数据的分布规律等理论上来说应该是一样的。
从上面我们可以得出,一般选择块时是在3B*B<C的限制下选取最大的块,但是实际上结果与我们预计的有点不符,从下面的结果可以看出,在块大小为32*32和64*64时结果都差不太多,所以并不是块大小越大越好,可能根据不同的机器会有不同的情况出现,但是可以肯定的是,在满足3B*B<C的条件下,块偏大一点运行的时间还是会快一点,但是不是最大的就是最快的。
(Release模式下,单位为ms)
SIZE\BLOCK |
8 |
16 |
32 |
64 |
128 |
256 |
512 |
261.5 |
248.2 |
238.5 |
237 |
260.5 |
262.5 |
1024 |
1977.5 |
1751.5 |
1706.3 |
1760 |
1944.2 |
1917.6 |
2048 |
17726.9 |
15108.1 |
14263.9 |
14507 |
15826.4 |
16070.1 |
4096 |
156833 |
123672 |
112854 |
111803 |
125633 |
130909 |
(3)矩阵的大小对程序执行时间的影响
关于这个问题是显而易见的,在块大小相同时,矩阵大小越大,分出来的块数量,在我们可以认为每一个块内执行乘法的时间是相同的条件下,块的数量越多,花费的时间也就越长。而在实际过程中由于缓存不命中等原因,可能块越多花费的时间更长。
所以矩阵越大,程序执行的时间越长。
(4)关于4096*4096矩阵大小的一些问题
实际上在运行程序时,我矩阵大小最大的时候设为了4096*4096。但是实际运行程序时发现这样运行程序有点不太现实了。从上面的结果可以看出,分块大小为8*8时,花费的时间为522227ms,即522.227s,将近9分钟,而我是利用程序运行10遍取平均值的机制,一个块我需要运行1个半小时,从8到256是6个测试数据,所以保守估计需要9个小时。然而在程序的运行过程中,过长的时间会导致很多问题,如突然间程序不占用CPU,或者突然死机等问题。我花费两天的时间运行了两次,都因为一些原因而没有跑出结果。后来是在Release模式下终于得到了结果,与预期的也比较相符。这也让我明白了,在遇到一些运行时间比较长的程序时,如果需要对其进行一些分析,可以用Release模式来运行程序。
6. 实验感想和总结
这个实验应该算是我四个大实验中花费时间最多的一个实验了,因为其中遇到了各种各样的问题,导致不断的去进行各种各样的测试,修改代码,更换系统等。虽然大部分的疑惑都已经解决,但是仍然存在着一些问题。不过我本人很享受这个过程,在这其中我体会到不断的找出问题,解决问题不断反复的过程对我的思考问题的方式有很大的提升。同时我对存储器的结构层次等有了更深入的了解。
在我完成这份报告后,我找到了前面遗留问题的一些解决方式,于是又返回来对报告重新进行了修改。这个过程也让我收获很多,在不断的交流中不断思考,找出其中的一些解决方案,还有就是不能太过于相信理论,认为和课本上不符的就一定是错的。毕竟程序运行时还有一些别的条件的影响,这次用Debug模式和Release模式就是一个很好的例子,希望自己以后也能够注意这一点。
四.矩阵转置
1. 实验要求和目的
实验要求:目前的矩阵转置在传统方法上总会存在着一个较大的Cache miss,所以本实验以灰度图为例,利用程序的局部性原理,想出两种方法来提高矩阵转置的效率,并用C/C++语言编写代码进行测试,对实验结果要有分析说明,并绘制出相应的图形。
实验目的:这个实验主要目的就是考察对时间局部性和空间局部性的理解,然后根据自身理解去寻找能够提高矩阵转置的效率的方法。检测自己是否能够将所学习的知识举一反三,灵活运用。
2. 实验测试条件说明
实验是在本人自己电脑上进行的测试,笔记本的相关信息如下:
型号: 宏碁 Acer Aspire VX5-591G
内存大小: 8G
CPU: Intel® Core™ i5-7300HQ CPU @2.50GHz 4核
高速缓存信息: L1 缓存:256KB
L2 缓存:1.0MB
L3 缓存:6.0MB
操作系统: Windows10 家庭版
3. 实验过程
本实验老师也给了一个网站去进行参考,但是网站上给出的SSE算法优化矩阵转置不是从程序局部性原理来解决问题的,也只能结合自身学习到的知识来想出解决的方法。
程序的局部性原理,在我们所学的知识中主要是分为时间局部性和空间局部性两种。把这两种知识运用到此实验中对应想出两种解决方案。时间局部性改进主要是提高缓存的命中率,而矩阵大小越大,缓存的命中率就越低,所以自然而然结合上一个分块矩阵乘法的时间想到可以进行分块矩阵转置。而对于空间局部性,主要是尽量的按照矩阵的存储方式去读写方能有良好的效率,但是此题比较特殊,不管采用哪种方式,在原矩阵和目标矩阵中,总会有一个矩阵的访问顺序是与存储方式相反的。但是从这点中我们也可以找出一些改进方式。我们通常的方式就是按照原矩阵(需要转置的矩阵)的存储方式先行后列循环,这样对于目标矩阵就是先列后行的顺序。但是如果我们转换一下,按照目标矩阵先行后列循环会不会效率高一点呢?根据后面的测试,可以给出肯定的答案。
所以我们根据分析得到了两种方法。一种是分块矩阵转置,一种是改变循环顺序,按照目标矩阵先行后列的顺序进行转置。
明确两种方式之后,我们就开始编写代码,主要是三个函数,原函数,分块转置函数,改变循环顺序的函数。编写完成后依次运行三个函数,得出运算结果,进行对比分析。
4. 实验结果
实验结果分为几个部分,首先是没有采用任何方法平常的矩阵转置(对应函数trans1),第二种是对循环顺序进行改变的结果(对应函数trans2),第三种是分块矩阵转置的结果(对应函数trans3)。在下面以表格的方式进行展示。实验测试为了方便,测试时矩阵均使用方阵。
在此说明,本实验在运行时使用的时Release模式,用Debug模式运行时,数据会比较奇怪,在后面实验分析中会进行说明。
(数据单位为ms)
函数\ SIZE |
256*256 |
512*512 |
1024*1024 |
2048*2048 |
4096*4096 |
Trans1 |
0.3 |
1.6 |
8.4 |
54.6 |
287.7 |
Trans2 |
0.1 |
1 |
6.6 |
51.5 |
194.3 |
Trans3: 8 |
0.2 |
1.2 |
6.8 |
49.4 |
211 |
Trans3: 16 |
0.1 |
0.8 |
6.3 |
44.7 |
207.4 |
Trans3: 32 |
0.1 |
0.8 |
7.7 |
44 |
207 |
Trans3: 64 |
0.1 |
1.4 |
6.3 |
43.3 |
207.3 |
Trans3: 128 |
0 |
1.6 |
6.2 |
43 |
208.2 |
Trans3: 256 |
0.1 |
1.2 |
6.3 |
43 |
207.4 |
5. 实验分析
由于此实验涉及到的方面也比较多,在此也分为几点来进行说明。
(1) Debug模式下和Release模式下的数据差别
在之前也已经提到过了两者的区别了,Release模式是对程序运行速度有优化的。我在两种模式下运行出来的结果如下(没有截图)。
Debug:
Trans1 0.4 3.3 17.3 85.7 397.3
Trans2 0.2 2.7 16.7 93 331.2
Release:
Trans1 0.3 1.6 8.4 54.6 287.7
Trans2 0.1 1 6.6 51.5 194.3
可以看到在Debug模式下对于矩阵大小为2048*2048时,改进的转置方法的用时反而大于未改进的。关于这一点,我多次运行的结果仍然是这样,这让我很奇怪,但是我又想不到能够解释这种情况的原因。多次尝试无果后,我采用Release模式运行时,结果就符合预期了,并且运行多次也是这个结果。关于这个现象,可能是我对这两种VS里的运行模式还不清楚,这个问题以后会继续进行研究,在此由于不影响最后的实验结果,就不多描述了。
(2)未改进的矩阵转置与在空间局部性上改进的矩阵转置的对比
在之前实验过程中,我们已经说明了空间局部性的改进是将原来的按原矩阵先行后列的方式改为按目标矩阵先行后列的方式进行循环。从结果中我们看出,对于此改进,在矩阵大小越大时,效果越明显,后者所执行的时间的确比前者要短,证明这个改进是有效的。
对此我个人有种分析。按照原来的方式,按照原矩阵先行后列,这样是在读取原矩阵时有良好的空间局部性,但是对于目标矩阵,写不命中率很高。而交换顺序后,时读取原矩阵时的读不命中率很高。个人感觉按照过程分析的话写不命中后的惩罚时间要比读不命中后的惩罚时间要长。所以在此是优先保证写命中,这样会在原来的基础上减少程序的运行时间,而结果也正是如此。
关于这个观点,后续还会继续进行探索研究,找出更准确实际的证据。
(3)未改进的矩阵转置与在时间局部性上改进的矩阵转置的对比
时间局部性的改进,就是将矩阵进行分块再在每块中进行转置。这样在时间局部性上有更好的表现,降低了其缓存的不命中率。这种方法也是根据前面的分块矩阵乘法得到的思路。
从结果上来看,分块矩阵转置的确能够减少程序所运行的时间,但是从结果中来看,与分块的大小并没有特别大的关系,块大小变化时,运行时间的变化不是很明显。这点也可作为继续研究的一项工作。
总而言之,即分块转置能够提高矩阵转置的效率,但是分块的大小对程序执行时间的影响并没有很大。
6. 实验总结与感想
这个实验也是花费了很多时间去进行测试和分析,其中也解决了很多问题,但也遗留了一些问题。在这个过程中我体会到,需要敢于提出自己的观点,如果连观点都没有,更不可能解决问题。提出观点后可以与别人讨论,探究,看方法是否可行,这个过程也是我最享受的过程,这让我觉得学到的知识还是有用的。希望今后能够保持这种态度,在更多的问题上去进行探索和研究。
在课本中的小实验就不需要像大实验那样介绍的如此详细了。所以在此把小实验都写在一起即可。
1. 课后习题6.7
题目要求:对一个三维数据进行求和,计算出程序运行的执行时间。同时需要改变循环的顺序和数组的大小,将得到的结果绘制成图形。
题目目的:主要是观察分析自己电脑的CPU的效率,加深自己对空间局部性等的理解。
实验过程:书上已经给出我们详细的代码,只需要编写主函数和计算时间的clock变量即可。编写完代码过后直接运行即可得到我们需要的结果。
实验结果: 由于小实验是很久之前就运行完,当时忘记了截图,只进行了手动记录,在此以表格的形式进行展示。
(数据单位为ms)
顺序\N值 |
10 |
50 |
100 |
500 |
1000 |
i, j, k |
0 |
0.5 |
3.4 |
290.2 |
2303.6 |
i, k, j |
0 |
0.7 |
4.1 |
521.5 |
5072.6 |
j, i, k |
0 |
0.5 |
4.3 |
346.9 |
2511.1 |
j, k, i |
0 |
0.4 |
6.4 |
943.6 |
13791.9 |
k, i, j |
0 |
0.6 |
11.8 |
2636.0 |
22026.7 |
k, j, i |
0 |
0.8 |
10.7 |
3169.8 |
30144.9 |
实验分析:
此题最初老师给的要求是N的大小从10到10W来进行增长,但是在实际的运行过程中在N=1000时,通过VS看到的内存占用就已经达到了3G,而我的电脑的内存才8G。所以运行最多到N=1000是极限了。
从图中的数据可以看出,循环顺序按照i, j, k进行运行时,花费的时间最短,按照k, j, i的顺序运行时花费的时间最长。在我们课上学到过,数组的存储是以行优先的方式存储的,对于三维数组,依次类推应为行最优先,列其次,纵向最次。所以i, j, k是按照最优的顺序访问的,这样程序就具有良好的空间局部性,所以花费的时间就最短,而其它的顺序都有和最优顺序不同的地方,而k, j, i是完全与其顺序相反,这样的空间局部性是最差的,所以花费的时间也就最长。
本题的主要目的就是探究空间局部性对程序执行时间的影响,而最终得到的实验结果也符合我们的预期。
实验感想:
此题其实没什么难度,但是由于最开始没有仔细研究数组所占的空间大小,导致一开始直接用10000去试运行程序,为此付出的代价就是电脑死机,然后重装系统。所以对于这类问题以后还是先想清楚一些细节问题为妙。
对于实验本身的结果,我个人没有任何异议。结果符合预期,也能够很好的证明空间局部性对程序运行时间的影响。这也让我们学到了以后在编写代码时如果能多加考虑这个问题的话,能让程序更快速的运行。
2. 课后习题6.8
题目要求:运行习题6.8中的三段代码,同时改变数组的大小N,将得到的结果绘制成图形,并分析说明。
题目目的:主要是加深对空间局部性的了解,了解空间局部性对程序执行时间的影响。
实验过程:直接按照书上给出的代码进行程序编写,然后改变N值的大小,运行程序将得到的结果绘制成图形即可。
实验结果:该程序也只是简单的做了一下数据的记录,没有截图,以表格的方式展示。
函数\N大小 |
1000 |
10000 |
100000 |
1000000 |
Clear1 |
0 |
0 |
2 |
18 |
Clear2 |
0 |
1 |
1 |
10 |
Clear3 |
0 |
0 |
7 |
53 |
实验分析:
其实此题的结果与理论上的分析有些出入。在理论上,函数clear1以步长为1的引用模式访问数组,是具有最好的空间局部性的。函数clear2在每个结构中,以步长不为1的模式跳到下列相对于结构起始位置的偏移处,所以函数clear2的空间局部性相较于clear1来说要差一点。而函数clear3,不仅在结构中跳来跳去,还在结构之间跳跃,所以函数clear3的空间局部性最差。
单从代码上我们可以作如上分析,但是实际的运行结果而言,函数clear2的运行时间却是最短的,不管是用Debug模式还是Release模式均是如此,所以这与我们之前分析的空间局部性的良好程度不太符合。
关于这个原因,可能是个人的猜想,或许是函数clear1中相比于clear2中多使用的一个循环的原因。可能一个循环执行的时间要比在结构体中跳跃到偏移处的时间要长,所以才导致实际运行起来函数clear2的时间要短于函数clear1的时间。关于验证尚还没有找到合适的方法。
这个例子也印证了一点,不是具有良好的空间局部性的代码就一定有着最快的运行时间。有的时候也受其它的一些因素影响。
实验总结和感想:
此题的实验结果与理论上分析的结果不相符,这也是让我花费了很大气力。不同系统下进行了测试,不同的运行模式等等。这也让我明白,理论分析也不一定就和实际结果相符,两者不符合时,也需要我们去找出一些能够解释这些现象的理由,这也是对我们的一种考验。
3. 课后习题6.17
题目要求:测试习题6.17的矩阵转置代码执行所需要的时间。需要改变矩阵大小进行多次测试,最后绘制出图形。
题目目的:主要是观察分析自己电脑的CPU的效率,加深自己对时间局部性等的理解。
实验过程:书上已经给出我们详细的代码,只需要编写主函数和计算时间的clock变量即可。编写完代码过后直接运行即可得到我们需要的结果。
实验结果:该程序也只是简单的做了一下数据的记录,并没有截图,仍然以表格的方式展示。
(数据单位为ms)
矩阵大小 |
2 |
4 |
8 |
16 |
32 |
64 |
128 |
256 |
用时 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0.2 |
512 |
1024 |
2048 |
4092 |
8192 |
16384 |
1 |
4.3 |
27.8 |
176.1 |
1085.1 |
6244.3 |
实验分析:
该实验实际上结果也是符合预期的,在不改变循环的顺序时,仅仅改变矩阵的大小,会导致时间局部性变差,因为缓存的不命中率会越来越高。所以在循环顺序不变时,矩阵大小增大时,运行的时间会逐渐变长。而结果也是符合理论的。
实验感想和总结:
本实验仅仅只是测试时间局部性对程序运行结果的影响,根据得出的数据,然后结合所学的知识,很容易得出数据结果是符合理论的。也可以看出时间局部性对程序运行时间的影响,所以以后编写程序时,要注意尽量保证缓存的命中率。
六.课本课后作业
习题6.37
在N=64时,sumA的不命中率为0.25, sumB为1, sumC为0.5
在N=60时,三者的不命中率都为0.25
习题6.41
每行4个字节,循环时后三个命中,第一个不命中,不命中率为0.25.