注:以下所有内容均来自开源学习组织DataWhale
存储器层级结构
存储器的层次结构如下所示:
其中,CPU寄存器保存最常用的数据。告诉缓存存储器作为主存储器的缓存,主存作为磁盘的缓存,磁盘作为网络中其他机器上数据的缓存。
基本存储技术
基本存储技术包括:SRAM存储器、DRAM存储器、ROM存储器、旋转硬盘、固态硬盘。
随机访问存储器(Random-Access Memory,RAM)
- 静态RAM(SRAM):SRAM将每个位存储在一个双稳态存储器单元中,每个单元用一个六晶体管电路实现。静态即该存储器单元可以无限期地保持在两个不同的电压配置之一,即只要有电SRAM存储器单元就会永远保持它的值,对干扰电压不敏感,干扰后可恢复。
- 动态RAM(DRAM):DRAM将每个位存储为对一个电容的充电,存储器单元由一个电容和一个访问晶体管组成。DRAM对干扰敏感,干扰后不会恢复。
传统DRAM
传统DRAM中,DRAM芯片中的单元划分为 d d d个超单元,每个超单元由 w w w个DRAM单元组成,所有的超单元被组织成一个 r × c r\times c r×c的长方形阵列( r c = d rc=d rc=d)。每个超单元有形如( i , j i,j i,j)的地址,i表示行,j表示列。如下图所示:
内存模块
在实际的计算机体系结构中,DRAM芯片被封装在内存模块中,并插到主板的扩展槽上,内存模块的基本思想是使用多个DRAM芯片,每个芯片中的超单元存储主存中的一个字节,并用相应超单元地址(i, j)的8个超单元来表示主存中字节地址A处的64位字。
例:要取出内存地址A处的一个字,内存控制器首先将A转化成一个超单元地址(i, j),并将它发送到内存模块,内存模块再将i和j广播到每个DRAM。然后每个DRAM输出它的(i, j)超单元的8位内容。模块中电路收集这些输出,并把它们合并成一个64位字,返回给内存控制器。
磁盘存储
RAM必须在有点的时候才能存数据,而磁盘可以永久存储大量数据。不过磁盘比DRAM读数据慢了10万倍,比SRAM慢了100万倍。
磁盘结构
磁盘由盘片和主轴组成,每个盘片都有两个表面,主轴可以使盘片转动,速率通常为5400-15000转每分钟。
盘片表面由一组磁道的同心圆组成。每条磁道由扇区和扇区间隙组成,每个扇区包含相等的数据位(通常为512字节),扇区间隙用于标识扇区的格式化位,不存储数据。
磁盘由一个或多个叠放的盘片组成,并密封在一个包装里。整个装置称为磁盘驱动器,简称磁盘,又称机械硬盘。
磁盘容量
磁盘容量决定因素:
-
记录密度(位/英寸):磁道一英寸的段可以放入的位数
-
磁道密度(道/英寸):从盘片中心出发半径上一英寸的段可以有的磁道数
-
面密度(位/平方英寸):记录密度与磁道密度的乘积
磁盘操作
磁盘通过读写头来读写存储在磁性表面的位,通过沿着半径方向移动传送臂,启动器可以将读写头定位在盘片的任何刺刀上,整个过程读写头垂直排列,一致行动。
磁盘读写数据的时间影响因素:
- 寻道时间:即传动臂定位目标磁道的时间。现在驱动器平均寻道时间位3-9ms,最高20ms。
- 旋转时间:在找到目标磁道后,盘片旋转到目标扇区可以读第一个位的时间。最长情况是盘片需要旋转一整圈。
- 传送时间:当目标扇区的第一个位位于读写头下时,驱动器开始读或写该扇区 的内容。一个扇区的传送时间依赖于旋转速度和每条磁道的扇区数目。
磁盘逻辑块
现代磁盘的架构中,通常将盘面上各个记录区编号成逻辑块序列。
当操作系统需要执行一个读写操作,例如读一个磁盘扇区的数据到主存时,操作系 统会将命令发送到磁盘控制器,让它读某个逻辑块号。控制器会先将逻辑块号翻译成一个三元组(盘面,磁道,扇区),这个三元组唯一对应一个物理扇区,随后将读写头移到相应扇区,然后将其内容复制到主存。
固态硬盘
固态硬盘(Solid State Disk,SSD)是一种基于闪存的存储技术,一个SSD由一个或多个闪存芯片及内存翻译层组成。
在SSD中,一个闪存由B个块组成,每个块又由P页组成。通常页的大小位512bytes - 4kb,块由32 - 128页组成,块的大小为16kb - 512kb。
数据在SSD中是以页为单位读写的。在数据写入前,需要对一页所属的块进行擦除。擦除即将块中所有位置置为1。写入的操作是将部分位置置为0。
SSD擦除次数过多后,块会因磨损而损坏。为了减少磨损,闪存翻译层采用平均磨损算法,来将擦除平均到各个块上。
局部性
- 空间局部性:良好的空间局部性是指如果某个数据在某个位置被引用了一次,那么在不远的将来将引用它附近的内存位置。
- 时间局部性:良好的时间局部性是指是被引用过一次的数 据项会在不远的将来再次被多次引用。
例如如下C代码:
int sumvec(int v[N]) {
int i, sum = 0;
for (i = 0; i < N; i++) sum += v[i];
return sum;
}
对于变量sum,sum在每次循环中都被引用一次,因此具有很好的时间局部性。对于向量v,v中的每个元素都被一个接一个的读取,因此该程序具有良好的空间局部性。综上,这个程序具有良好的局部性。
对如下代码:
int sumarrayrows(int a[M][N]) {
int i, j, sum = 0;
for (i = 0; i < M; i++)
for (j = 0; j < N; j++)
sum += a[i][j];
return sum;
}
二位数组按照行优先顺序读取数组中元素,同时二维数组也是按照行优先顺序存储的,因此该函数具有良好的空间局部性。但其中每个元素只被使用一次,因此这个程序不具备良好的时间局部性。
对于如下代码:
int sumarrayrows(int a[M][N]) {
int i, j, sum = 0;
for (j = 0; i < N; j++)
for (i = 0; i < N; i++)
sum += a[i][j];
return sum;
}
由于行优先访问被改为了列优先访问,因此这个程序就没有了良好的空间局部性了。
步长对局部性的影响
顺序访问数组即为“步长为1的引用模式”,如果每隔k个元素进行访问,则称为“步长为k的引用模式”。一般来说,步长越大,空间局部性越差。
存储器层次结构
位于k层的更快更小的存储设备作为位于k+1层更大更慢的存储设备的缓存。
在存储器层次结构中,第k+1层存储器被划分为连续的数据对象块(chunk),称为块(block)。每个块有唯一的地址或名字。在第k层,存储器被划分为较少的块的集合,每个块的大小与k+1层中块的大小相同。
- 缓存命中:当程序需要第k+1层的某个数据对象d时,它首先在当前存储在第k层的一个块中查找d。如果d刚好缓存在k层中,则称为缓存命中。这是程序会直接从第k层读取d。
- 缓存不命中:如果在第k层中找不到数据对象d,即缓存不命中。当缓存不命中,就需要从第k层中取出包含d的块,如果k层缓存满了,就要覆盖现有的一个块。
- 冷缓存:如果一个缓存是空的,则称其为冷缓存。冷缓存对任何数据对象的访问都不命中,称为强制性不命中或冷不命中。
如果发生了缓存不命中,则第k层的缓存就必须执行某个放置策略,来确定把从第k+1层中取出来的块放置的位置。
通用高速缓存存储器组织结构
对于一个计算机系统,其中每个存储器地址有m位,共有 M = 2 m M=2^m M=2m个不同的地址。这样的一个高速缓存被组织成一个有 S = 2 s S=2^s S=2s个告诉缓存组的数组。每个组包含E个高速缓存行。每个行包含 B = 2 b B=2^b B=2b字节的数据块。其中还包括一个有效位,用于指明这个行是否包含有意义的信息。 t = m − ( b + s ) t=m-(b+s) t=m−(b+s)唯一地表示存储在这个告诉缓存行中的块。
高速缓存结构可以用一个四元组 ( S , E , B , m ) (S,E,B,m) (S,E,B,m)描述。高速缓存大小 C = S × E × B C=S\times E\times B C=S×E×B表示所有块的大小的和。
高速缓存操作分为三步:组选择、行匹配、字抽取。