这两天看了《CSAPP》的第6章,主要讲的是存储器层次结构,环顾内存管理器系列的文档,发现确实没有一个终结性的小结内存管理体系的东西,所以这次正好借着小结读书笔记的机会正好总结。
我认为计算机的两个比较重要的问题,一个是运算,一个是存储。其实图灵最早的设计也是如此,我也学习学习伟人的思想。存储器系统其实是一个根据容量,速度,成本,访问时间,读取时间,效率等等根据不同特点的不同部分的一个复杂的体系。
举个例子:
L0 : 寄存器
L1 : L1高速缓存
L2 : L2高速缓存
L3 : L3高速缓存
L4 : 主存(内存条)
L5 : 本地磁盘
L6 : 分布式文件系统, Web等
以上简单说明了7个级别的存储结构,从上到下成本更低,体积更大,速度越慢。
SRAM(CPU 的高速缓存) ,将每个位存储在一个双稳态的存储器单元里面。每个单元是用一个六晶体管,只要有电就可以工作,即使受到干扰,当干扰结束的时候,它依然会恢复到之前的值。
DRAM (主存) ,将每一位存储为对一个电容充电,但是这个存储器会收到干扰,并且不能长时间的保持数据存在,但是计算机运行的时钟周期是以纳秒来衡量的,存储系统必须周期性的读出数据刷新内存条。
DRAM 芯片中的单元被分成d个超单元,,每个单元由W个DRAM 单元组成。一个d*w的DRAM 总共存储dw位信息。
这个是存储器是通过引脚来确定来传送行和列的值,然后通过数据总线传出传入数据。
闪存基于EEPROM 是一类非易失性存储器,它已经在我们的生活中很常见了,比如U盘,SSD固态应硬盘,速度确实提高了不少,但是寿命有限,根据写的次数增多,很多存储块逐渐的被损坏。
这里记载一个《CSAPP》的例子好了。
movl A ,%eax
地址A的内容首先被加载到寄存器%eax中,CPU芯片称为总线接口,首先CPU 把地址A放到系统总线上I/O桥将信号传递到存储器总线,其次主存收到地址总线上的信号,从存储器总线读取地址,从DRAM取出数字,并将数据写到存储器总线。I/O桥将存储器总线信号翻译成系统总线信号,然后沿着系统总线传输,最后CPU 得到信号。从系统总线上读取数据,并将数据拷贝到寄存器。
一般而言,高速缓存是一个小而快速的存储设备,它作为存储在更大也更慢的设备中的数据对象的缓冲区域。使用高速缓存的过程称为缓存。
数据总是以块大小为传递单元,在相邻的层次之间来回拷贝,但是在每一个层次之中的数据块的大小是固定的,应该是统一数据块大小可以增加访问速率的原因。
如果需要查找一个数据的时候,首先在这个缓存中查找信息,如果找到信息就是缓存命中,如果没有找到就是缓存未命中,当缓存未命中后,程序就需要从数据存储相应的存储结构得到数据然后拷贝 到缓存中来,此时相应的上一级存储结构就会被替换,替换的数据块称为“牺牲块”,至于如何找出“牺牲”块,其中由一个相应的替换策略。这个策略有很多种可能的实现方式。
类型 缓存什么 被缓存在何处 延迟(周期) 由谁管理
CPU寄存器 4/8B 芯片的CPU寄存器 0 编译器
TLB 地址翻译(旁路缓冲区) 芯片上的TLB 0 硬件MMU
L1 64字节块 芯片上的L1高速缓存 1 硬件
L2 64字节块 芯片上/下的L2高速缓存 10 硬件
L3 64字节块 芯片上/下的L3高速缓存 30 硬件
虚拟存储器 4KB页 主存 100 硬件+os
缓冲区缓存 部分地址空间 主存 100 os
磁盘缓存 磁盘扇区 磁盘控制器 100 000 控制器固件
网络缓存 部分文件 本地磁盘 10 000 000 AFS/NFS 客户
浏览器缓存 Web 页 本地磁盘 10 000 000 Web 浏览器
Web 缓存 Web 页 远程服务器磁盘 1 000 000 000 Web 代理服务器
AFS : 安德鲁文件系统
局部性原理在很多地方都有“拯救”程序的意义,这里简单说下。一般使用使用局部性好的程序都倾向于访问程序频繁访问的内存位置,更或者说倾向于使用已经访问的临近的数据项。这是一个持久性的概念,对于硬件和软件都有很重要的意义。
局部性主要表现在两个方面:时间局部性,空间局部性。跟直接的说局部性原理其实就是尽最大的可能引用缓存,减少寻址的时间,减少寻址动作可以减少很多事情的,减少寻址动作就是减少了页错误,减少中断,减少数据拷贝。所以说一个有良好局部性的程序比局部性差的程序运行的更快。
对程序数据引用的局部性,举个例子,比如一个二维数组求和
[c]
int sum(int a[M][N]){
int i,j;
int sum = 0;
for(i = 0;i < M;i++){ /*这样其实就是利用了局部性,如果两重循环为 先N 后M则就没有使用好局部性*/
for(j = 0;j < N;j++){
sum += a[i][j];
}
}
return sum;
}
[/c]
还有取指令的局部性,因为指令也是存储在寄存器中的,CPU必须取出这些指令,所以像使用循环这种指令,当循环体比较小的时候,程序的局部性会非常的好。
早期计算机存储体系: CPU寄存器 DRAM内存条 磁盘存储。随着CPU与内存的的性能差距不断的增大,人们逐步加入了速度依次递减,容量依次增大的“缓存区”其实还是使用了局部性的优势,但是上图有详细的缓存参数,可见设计的改动也是利用了局部性原理。
当下计算机体系中都有高速缓存,我们现在就考虑一种通用的高速缓存的例子。
首先,每个存储器的地址有m 位,那么总共可以生成M= 2^m 个地址,如图这样一个高速缓存组有S= 2 ^s 个高速缓存组,每个组有E个高速缓存行,每行有B = 2 ^ b个字节的数据块,还有t = m-b-s个标记位,他们唯一地标识存储在这个高速缓存行中的块。
高速缓存(S,E,B,m)
高速缓存是一个高速缓存组的数组,每个组包含一个或多个行,每个行有一些有效位和一些标记位,以及一个数据块。
高速缓存的结构将m个地址位划分成为了t 个标记位,s 个组索引位,和b 个块偏移位。
一般而言,高速缓存的结构可以使用元祖(S,E,B,m)来描述。高速缓存的大小C指的是所有块的大小的和。
C = S*E*B
下边说下一个高速缓存的参数小结
基本参数
参数 描述
S= 2^s 组数
E 每个组的行数
B = 2^b 块大小
m = log2(M) 物理地址位数
M = 2^m 存储器地址的最大数量
s = log2(S) 组索引位的数量
b = log2(B) 块偏移位数量
t = m- (b+s) 标记位数量
C = B*E*S 纯的高速缓存大小
直接映射高速缓存:
就是每个组只有一个高速缓存行,他有一个CPU,一个寄存器文件,一个L1高速缓存,和一个主存,当CPU执行一条读存储器字w的指令,它指向L1高速缓存请求这个字。如果L1高速缓存有这个东西,那么直接就是缓存命中,高速缓存直接给CPU传递。如果不命中则L1 向主存请求数据,然后从主存拷贝到高速缓存行中,再传给CPU。
宏观上分为三步:
1.组选择
2.行匹配
3.字抽取
比如以下例子:
每一个位段都是定位的信息。
注意:
使用中间位做组索引,实际的工程中一般都是使用中间位作索引的,因为高位很有可能短期内不会变化,在任何时刻内高速缓存可能都只保存一个块大小的内容,这样对高速缓存使用效率会很低,所以我们需要可能的将相邻的块映射到不同的高速缓存上。这样命中率会提高,减少或防止“抖动” 的发生。
组相联高速缓存:每个组包含的行数多余1
全相联高速缓存:只有一个组但是却有多个行
这是core i7的高速缓存结构体系。每个CPU核心具有自己相应的L1L2高速缓存,其中L1高速缓存分为指令缓存和数据缓存。L3是共享的高速缓存。所以L1L2的缓存相对提升大小,可以提高性能。所以以后买电脑多关心下L1和L2。
最后说说几个影响高速缓存的东西:
其实计算机的设计一直都是一个折中的过程!!!!
1.高速缓存大小
高速缓存越大自然更快但是太大,寻址复杂性又会增加,对于L1来说,速度永远是第一位的,所以L1不能过于巨大。
2.块大小
块越大存储的信息越多,但是不命中惩罚时间也会越长。
3.相联度
一般高速响应的相联度会较低,低速的则较大,L1L28路相联,但是L3却是16路相联。
4.写策略
简而言之,高速缓存越往下层,越可能使用写回策略而不是直写策略。
参考:<
查看原文:http://zmrlinux.com/2016/01/16/%e5%86%85%e5%ad%98%e7%ae%a1%e7%90%86%e5%99%a8%ef%bc%88%e5%8d%81%e4%b9%9d%ef%bc%89%e5%ad%98%e5%82%a8%e5%99%a8%e5%b1%82%e6%ac%a1%e7%bb%93%e6%9e%84/