2017-2018-1 20155332 《信息安全系统设计基础》第14周学习总结
2017-2018-1 20155332 《信息安全系统设计基础》第14周学习总结
学习目标
找出全书你认为学得最差的一章,深入重新学习一下,要求(期末占5分):
- 总结新的收获
- 给你的结对学习搭档讲解或请教,并获取反馈
- 参考上面的学习总结模板,把学习过程通过博客(随笔)发表,博客标题“学号 《信息安全系统设计基础》第十四周学习总结”,博客(随笔)要通过作业提交,截至时间本周日 23:59。
-
收获
在简单模型中,存储器系统是一个线性的字节数组,而cpu能够在一个常数时间内访问每个存储器位置。
实际上,存储器系统是一个具有不同容量、成本和访问时间的存储设备的层次结构。
存储器层次结构是可行的。整体效果是一个大的存储器池,其成本与层次结构底层最便宜的存储设备相当,但是却以接近于层次结构顶部存储设备的高速率向程序提供数据。
计算机系统中一个基本而持久的思想:如果你理解了系统是如何将数据在存储器层次结构中上下移动的,那么你就可以编写你的应用程序,使得它们的数据项存储在层次结构较高的地方,在那里cpu能更快的访问它们。
这个思想围绕着计算机程序的一个称为局部性的基本属性。
在本章中,我们会看看基本的存储技术,并描述它们是如何被组织成层次结构的。
教材学习内容总结
存储技术
随机访问存储器分为两类:静态,动态。SRAM比DRAM更快,更贵。SRAM作为高速缓存存储器。DRAM作为主存。
SRAM将每个位存储在一个双稳态存储单元里。只要有电,它就保持他的值。
DRAM将每个位存储为对一个电容的充电。DRAM单元在10-100ms内失去电荷,所以需要刷新或者其他的方法保证数据正确性。
DRAM表示一般写成NW,w是一个单元的位数,N是单元数。本身称这个单元是超单元。然后N又表示为rc,这样的表示是寻址的时候地址引脚不会太多,比如N=16,就要4个引脚。4*4的话,4需要两个引脚。但是这种二维阵列组织有个缺点:两步发送地址,增加了访问时间。
DRAM的增强型有:FPM DRAM,EDO DRAM,SDRAM,DDR SDRAM(DDR,DDR2,DDR3),Random DRAM,VRAM。
PC使用的DRAM历史:95之前FPM,96-99EDO,-02SDRAM和DDR,-10DDR3。
非易失性存储器,SRAM和DRAM都是易失的。
ROM称为只读存储器是历史原因,很多ROM都是可写的。ROM的分类是以它们能够被重编程(写)的次数和对他们进行重编程的机制来划分的。
ROM包括:PROM,EPROM,EEPROM,闪存(基于EEPROM)。
存储在ROM设备中的程序通常称为固件。
总线事物:读事物——从主存传送数据到cpu,写事物——从cpu传送数据到内存。
磁盘是广为应用的保存大量数据的存储设备:盘片,表面,主轴,RPM,磁道,扇区,柱面。
对于SRAM和DRAM,KMGT通常是1024为基,但对于磁盘,KMGT以1000为基。
像图形卡,监视器,鼠标,键盘和磁盘这样的I/O设备,都是通过I/O总线(PCI)连接到cpu和主存的。
系统总线和存储器总线是与cpu总线相关的,但pci这样的总线和底层cpu无关。
I/O总线总是比系统总线和存储器总线慢,但是它可以容纳种类繁多的第三方I/O设备。主机总线适配器将一个或多个磁盘连接到I/O总线,最常用的是SCSI和SATA,前者更贵,更快。
cpu使用一种称为存储器映射I/O的技术,在使用该技术的系统中,地址空间中有一块地址是为与I/O设备通信保留的。每个这样的地址称为一个I/O端口。当一个设备连接到总线上时,它与一个或多个端口相联系。
cpu从磁盘读数据时发生的步骤:cpu通过将命令、逻辑块号和目的存储器地址写到与磁盘相关联的存储器映射地址,发起一个磁盘读;磁盘控制器读扇区,并执行到主存的DMA传送;DMA传送完成时,磁盘控制器用中断的方式通知CPU。
现代磁盘将他们的构造呈现为一个简单的视图,一个B个扇区大小的逻辑块序列。磁盘控制器维护着逻辑块号和实际(物理)磁盘扇区之间的映射关系。这里看到,逻辑块对应扇区。
SSD包,由一个或多个闪存芯片和闪存翻译层组成。
存储器和磁盘技术的一个基本事实:增加密度比降低访问时间更容易。
DRAM和磁盘的性能滞后于cpu的性能。现代计算机频繁的使用基于SRAM的高速缓存,试图弥补处理器-存储器之间的差距。这种方法可行是因为应用程序的局部性。
局部性
一个编写良好的计算机程序尝尝具有良好的局部性。也就是说,他们倾向于引用临近于其他最近引用过的数据项的数据项,或者最近引用过的数据项本身。
这种倾向性,被称为局部性原理,是一个持久的概念,对硬件和软件系统的设计和性能都有着极大的影响。
局部性通常有两种不同的形式:时间局部性和空间局部性。
两者的区别在于时间对的是一个存储器位置,空间对的是附近的存储器位置。
被引用过一次的存储器位置很可能在不远的将来再被多次引用——时间。一个存储器位置被引用了一次,那么程序很可能在不远的将来引用附近的一个存储器位置——空间。
对于空间局部性,步长为1的引用模式称为顺序引用模式,这种模式具有最好的空间局部性,如数组,一个一个的顺序访问具有最好的空间局部性。
多维数组,按照行优先顺序,具有最好的空间局部性。
量化评价一个程序中局部性的简单原则:
- 重复引用同一个变量的程序有良好的时间局部性。
- 对于具有步长为k的引用模式的程序,步长越小,空间局部性越好。具有步长为1的引用模式的程序有很好的空间局部性。
- 对于取指令来说,循环有好的时间和空间局部性。循环体越小,循环迭代次数越多,局部性越好。
存储器层次结构
存储器层次结构是组织存储器系统的方法,人想的,现在所有的计算机系统都使用了这种方法。
一般而言,高速缓存是一个小而快速的存储器设备,它作为存储在更大,也更慢的设备中的数据对象的缓冲区域。使用高速缓存的过程称为缓存。
存储器层次结构的中心思想是:对于每个k,位于k层的更快更小的存储设备作为位于k+1层的更大更慢的存储设备的缓存。
也就是说层次结构的每一层都缓存来自较低一层的数据对象。
这里必须强调一下,这也就是说数据时不可以越级的吗?
数据总是以块大小为传送单元在第k层和第k+1层之间来回拷贝的。例子:L1和L0的传送是1个字的块,L2和L1之间是8-16个字的块。
一些概念:缓存命中,缓存不命中,牺牲块,替换策略,冷缓存,强制性不命中,冷不命中,放置策略,冲突不命中,容量不命中。
- 编译器管理寄存器文件,缓存层次结构的最高层。
- L1,L2和L3的缓存完全是由内置在缓存中的硬件逻辑来管理的。
- DRAM主存作为存储在磁盘上的数据块得缓存,是由操作系统和cpu上的地址翻译硬件共同管理的。
- AFS这样的分布式文件系统,本地磁盘作为缓存,它是由运行在本地机器上的AFS客户端进程管理的。
概括来说,基于缓存的存储器层次结构行之有效,是因为较慢的存储设备比较快的存储设备更便宜,同时,还因为程序往往展示局部性。
高速缓存存储器
早期计算机系统的存储器层次结构只有三层:cpu寄存器,DRAM主存,磁盘。随着cpu和主存之间差距的逐渐增加,系统设计者被迫加入了SRAM高速缓存存储器,称为L1/L2/L3高速缓存。
L1通常需要2-4个时钟周期,L2通常需要10个时钟周期,L3通常需要30-40个时钟周期。
高速缓存存储器结构的一些细节:直接映射高速缓存,组相联高速缓存,全相联高速缓存。
高速缓存参数的性能影响:不命中率,命中率,命中时间,不命中处罚。
(这一节,了解一些硬件知识,结构的细节不管了)
编写高速缓存友好的代码
局部性比较好的程序更容易有较低的不命中率,而不命中率较低的程序往往比不命中率较高的程序运行的更快。
下面就是我们用来确保代码高速缓存友好的基本方法:
让最常见的情况运行得快。程序通常把大部分时间都花在少量的核心函数上,而这些函数通常把大部分时间都花在了少量循环上。
每个循环内部缓存不命中数量最小。(对局部变量的反复引用是好的,步长为1的引用模式是好的)
综合:高速缓存对程序性能的影响
存储器系统的性能不是一个数字就能描述的,相反,它是一座时间和空间局部性的山,这座山的上升高度差别可以超过一个数量级。明智的程序员会试图构造他们的程序,使得程序运行在山峰而不是低谷。
还是这几项,推荐的技术:
将你的注意力集中在内循环上,大部分计算和存储器访问都发生在这里。
通过按照数据对象存储在存储器中的顺序、以步长为1的来读数据,从而使得你程序中的空间局部性最大。
一旦从存储器中读入了一个数据对象,就尽可能多的使用它,从而使得程序中的时间局部性最大。
6.7 小结
- 基本存储技术包括RAM,ROM和磁盘。
- RAM有两个类型
- SRAM
- DRAM
- SDRAM
- DDR
- 程序通过编写良好的局部性代码利用好缓存。
教材学习中的问题和解决过程
问题1:存储技术分为哪几种
1.随机访问存储器(RAM, Random-Access Memory)
- SRAM,静态,双稳态
- DRAM,动态,敏感,存储器系统需要周期性地读写刷新存储器的每一位。(RAM, Random-Access Memory)
2.DRAM阵列
- DRAM组成二维阵列而不是一维线性数组的一个原因是降低芯片上地址引脚的数量。例如,16个超单元组成的阵列,二维和一维分别需要2个和4个地址引脚。二维组织的缺点是,地址必须分两步发送,增加了访问时间。(一个行地址,一个列地址)。
- 再加一个存储控制器和缓存行,收到行地址,读取整行放入缓存行,之后收到列地址,返回某一个超单元的数据。
3.访问主存
系统总线是一组并行的导线,能携带地址、数据和控制信号。但是不同总线不能直接互通,这就用到了I/O桥。
问题2:对扇区的访问时间分为哪几部分,
-
寻道时间:将磁头定位到目标扇区所在的磁道。这个时间依赖于磁头之前的位置和传动臂在盘面上移动的速度。通常3~9ms。
-
旋转时间:找到目标所在的第一个扇区。性能依赖于当前到达的磁道上相对目标扇区的位置和磁盘的旋转速度。
传送时间:读写扇区内容的时间。依赖于旋转速度和当前磁道的扇区数目。
问题三:高速缓存器有什么作用
1.基本构造
(S, E, B, m),m是地址w的位长度。
- S,S=2^s个组。
- E,每组E个高速缓存行。
- B,每个缓存行作为一个数据块,有B=2^b个字节。地址w的最后b位是块偏移。设计得真是巧啊,配合组号,正好可以把一段连续内存地存放在连续的缓存块里。
- 1位有效位,指明该行是否有效。
- t位标记位,t=m-b-s。唯一标识一个缓存行(数据块)
容量计算:
高速缓存确定一个请求是否命中,然后取出被请求的字的过程,分为三步:1)组选择,2)行匹配,3)字抽取。当且仅当设置了有效位,而且标记位与w地址中的标记位相匹配时才算命中。
2.分类
- 直接映射高速缓存,每个组只有一行。
- 组相联高速缓存,每个组不止一行。
- 全相联高速缓存,只有一个组,所有的缓存行都在一个组里。
代码调试中的问题和解决过程
6.38
N=64时:
sumA: 1/4
sumB: 1
sumC: 1/2
N= 60时:
sumA ,sumB,sumC的缓存不命中率均为 1/4
比较难判断的是N = 60时sumB的缓存不命中率(sumC与sumB是一样的),我写了一个函数返回不命中次数,将形参n赋值60即可。
//高速缓存命中率函数,返回不命中次数
int noHitPercentage(int n)
{
//不命中的次数
int result = 0;
//总共要循环的次数
int count;
//存储块的标记位
int a[256];
for(int i =0;i < 256;i++)
{
a[i] = -1;
}
for(int j = 0;j < n;j++)
for(int i = 0;i < n;i++)
{
//求出这个数的相对索引
count = i * n + j;
//求这个索引对应的块号
int blockNo = (count/4) % 256;
//求出标记t
int t = (count/4)/256;
//如果标记位不相等则不明中
if(t != a[blockNo])
{
a[blockNo] = t;
result++;
}
}
return result;
}
6.46
void betterTranspose(int *dst,int *src,int dim)
{
<span style="white-space:pre"> </span>int i, j;
<span style="white-space:pre"> </span>int iCount,jCount;
<span style="white-space:pre"> </span>//以4 * 4 的方阵为单位依次计算,增加了写的缓存命中率,多个元素一起读写还减少了循环开销
<span style="white-space:pre"> </span>for(i = 0;i < dim - 3;i += 4)
<span style="white-space:pre"> </span>{
<span style="white-space:pre"> </span>iCount = i * dim;
<span style="white-space:pre"> </span>for(j = 0;j < dim - 3;j += 4)
<span style="white-space:pre"> </span>{
<span style="white-space:pre"> </span>jCount = j * dim;
<span style="white-space:pre"> </span>dst[jCount + i] = src[iCount + j]; //dst[j][i] = src[i][j]
<span style="white-space:pre"> </span>dst[jCount + i + 1] = src[iCount + dim + j]; //dst[j][i + 1] = src[i + 1][j]
<span style="white-space:pre"> </span>dst[jCount + i + 2] = src[iCount + dim * 2 + j]; //dst[j][i + 2] = src[i + 2][j]
<span style="white-space:pre"> </span>dst[jCount + i + 3] = src[iCount + dim * 3 + j]; //dst[j][i + 3] = src[i + 3][j]
<span style="white-space:pre"> </span>dst[jCount + dim + i] = src[iCount + j + 1]; //dst[j + 1][i] = src[i][j + 1]
<span style="white-space:pre"> </span>dst[jCount + dim + i + 1] = src[iCount + dim + j + 1]; //dst[j + 1][i + 1] = src[i + 1][j + 1]
<span style="white-space:pre"> </span>dst[jCount + dim + i + 2] = src[iCount + dim * 2 + j + 1]; //dst[j + 1][i + 2] = src[i + 2][j + 1]
<span style="white-space:pre"> </span>dst[jCount + dim + i + 3] = src[iCount + dim * 3 + j + 1]; //dst[j + 1][i + 3] = src[i + 3][j + 1]
<span style="white-space:pre"> </span>dst[jCount + dim * 2 + i] = src[iCount + j + 2]; //dst[j + 2][i] = src[i][j + 2]
<span style="white-space:pre"> </span>dst[jCount + dim * 2 + i + 1] = src[iCount + dim + j + 2]; //dst[j + 2][i + 1] = src[i + 1][j + 2]
<span style="white-space:pre"> </span>dst[jCount + dim * 2 + i + 2] = src[iCount + dim * 2 + j + 2]; //dst[j + 2][i + 2] = src[i + 2][j + 2]
<span style="white-space:pre"> </span>dst[jCount + dim * 2+ i + 3] = src[iCount + dim * 3 + j + 2]; //dst[j + 2][i + 3] = src[i + 3][j + 2]
<span style="white-space:pre"> </span>dst[jCount + dim * 3 + i] = src[iCount + j + 3]; //dst[j + 3][i] = src[i][j + 3]
<span style="white-space:pre"> </span>dst[jCount + dim * 3 + i + 1] = src[iCount + dim + j + 3]; //dst[j + 3][i + 1] = src[i + 1][j + 3]
<span style="white-space:pre"> </span>dst[jCount + dim * 3 + i + 2] = src[iCount + dim * 2 + j + 3]; //dst[j + 3][i + 2] = src[i + 2][j + 3]
<span style="white-space:pre"> </span>dst[jCount + dim * 3 + i + 3] = src[iCount + dim * 3 + j + 3]; //dst[j + 3][i + 3] = src[i + 3][j + 3]
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>//记录当前行和列的索引,以便执行完剩余的项
<span style="white-space:pre"> </span>int curIndex = i;
<span style="white-space:pre"> </span>//处理剩余项,简单的交换处理
<span style="white-space:pre"> </span>for(i = 0;i < curIndex;i++)
<span style="white-space:pre"> </span>for(j = curIndex;j < dim;j++)
<span style="white-space:pre"> </span>{
<span style="white-space:pre"> </span>dst[j * dim + i] = src[i * dim + j];
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>for(i = curIndex;i < dim;i++)
<span style="white-space:pre"> </span>for(j = 0;j < dim;j++)
<span style="white-space:pre"> </span>{
<span style="white-space:pre"> </span>dst[j * dim + i] = src[i * dim + j];
<span style="white-space:pre"> </span>}
}
6.47
void better_col_convert(int *G,int dim)
{
int i, j;
int iCount,jCount;
//以4 * 4 的方阵为单位依次计算,增加了写的缓存命中率,多个元素一起读写还减少了循环开销
for(i = 0;i < dim - 3;i += 4)
{
iCount = i * dim;
for(j = 0;j < dim - 3;j += 4)
{
jCount = j * dim;
G[jCount + i] = G[iCount + j] || G[jCount + i]; //G[j][i] = G[i][j] || G[j][i]
G[jCount + i + 1] = G[iCount + dim + j] || G[jCount + i + 1]; //G[j][i + 1] = G[i + 1][j] || G[j][i + 1]
G[jCount + i + 2] = G[iCount + dim * 2 + j] || G[jCount + i + 2]; //G[j][i + 2] = G[i + 2][j] || G[j][i + 2]
G[jCount + i + 3] = G[iCount + dim * 3 + j] || G[jCount + i + 3]; //G[j][i + 3] = G[i + 3][j] || G[j][i + 3]
G[jCount + dim + i] = G[iCount + j + 1] || G[jCount + dim + i]; //G[j + 1][i] = G[i][j + 1] || G[j + 1][i]
G[jCount + dim + i + 1] = G[iCount + dim + j + 1] || G[jCount + dim + i + 1]; //G[j + 1][i + 1] = G[i + 1][j + 1] || G[j +1][i + 1]
G[jCount + dim + i + 2] = G[iCount + dim * 2 + j + 1] || G[jCount + dim + i + 2]; //G[j + 1][i + 2] = G[i + 2][j + 1] || G[j +1][i + 2]
G[jCount + dim + i + 3] = G[iCount + dim * 3 + j + 1] || G[jCount + dim + i + 3]; //G[j + 1][i + 3] = G[i + 3][j + 1] || G[j + 1][i + 3]
G[jCount + dim * 2 + i] = G[iCount + j + 2] || G[jCount + dim * 2 + i]; //G[j + 2][i] = G[i][j + 2] || G[j +2][i]
G[jCount + dim * 2 + i + 1] = G[iCount + dim + j + 2] || G[jCount + dim * 2 + i +1]; //G[j + 2][i + 1] = G[i + 1][j + 2] || G[j +2][i + 1]
G[jCount + dim * 2 + i + 2] = G[iCount + dim * 2 + j + 2] || G[jCount + dim * 2 + i + 2]; //G[j + 2][i + 2] = G[i + 2][j + 2] || G[j +2][i + 2]
G[jCount + dim * 2+ i + 3] = G[iCount + dim * 3 + j + 2] || G[jCount + dim * 2 + i + 3]; //G[j + 2][i + 3] = G[i + 3][j + 2] || G[j + 2][i + 3]
G[jCount + dim * 3 + i] = G[iCount + j + 3] || G[jCount + dim * 3 + i]; //G[j + 3][i] = G[i][j + 3] || G[j +3][i]
G[jCount + dim * 3 + i + 1] = G[iCount + dim + j + 3] || G[jCount + dim * 3 + i + 1]; //G[j + 3][i + 1] = G[i + 1][j + 3] || G[j +3][i + 1]
G[jCount + dim * 3 + i + 2] = G[iCount + dim * 2 + j + 3] || G[jCount + dim * 3 + i + 2]; //G[j + 3][i + 2] = G[i + 2][j + 3] || G[j + 3][i + 2]
G[jCount + dim * 3 + i + 3] = G[iCount + dim * 3 + j + 3] || G[jCount + dim * 3 + i + 3]; //G[j + 3][i + 3] = G[i + 3][j + 3] || G[j + 3][i + 3]
}
}
//记录当前行和列的索引,以便执行完剩余的项
int curIndex = i;
//处理剩余项,简单的交换处理
for(i = 0;i < curIndex;i++)
for(j = curIndex;j < dim;j++)
{
G[j * dim + i] = G[i * dim + j] || G[j * dim + i];
}
for(i = curIndex;i < dim;i++)
for(j = 0;j < dim;j++)
{
G[j * dim + i] = G[i * dim + j] || G[j * dim + i];
}
}
(statistics.sh脚本的运行结果截图)
结对及互评
点评模板:
- 博客中值得学习的或问题:
- xxx
- xxx
- ...
- 代码中值得学习的或问题:
- xxx
- xxx
- ...
- 其他
本周结对学习情况
- [20155324](博客链接)
- 结对照片
- 结对学习内容
- XXXX
- XXXX
- ...
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第十四周 | 3000/5000 | 1 /26 | 15/320 |
尝试一下记录「计划学习时间」和「实际学习时间」,到期末看看能不能改进自己的计划能力。这个工作学习中很重要,也很有用。
耗时估计的公式
:Y=X+X/N ,Y=X-X/N,训练次数多了,X、Y就接近了。
-
计划学习时间:XX小时
-
实际学习时间:XX小时
-
改进情况:
(有空多看看现代软件工程 课件
软件工程师能力自我评价表)