存储器结构围绕着局部性:具有良好局部性的程序倾向于访问邻近的数据项集合。
随机访问存储器(Random-Access-Memory)
静态RAM
- 只要有电就保持不变无需刷新。
- 读取速度快。
- 对干扰不敏感。
- 造价贵,主要用于高速缓存存储器。
动态RAM
- 对干扰敏感,由于会漏电而需要周期性的刷新。
- 读取速度较慢。
- 造价便宜,可用作主存和帧缓冲区。
为何将DRAM设计成二维矩阵而非线性数组:减少芯片地址上的引脚数。
增强的DRAM
基于传统DRAM,进行优化,改进访问速度。
- 快页模式DRAM(Fast Page Mode DRAM,FPM DRAM)。
传统的DRAM将超单元的一整行拷贝到它的内部缓冲区,使用一个,然后丢弃剩余的。
FPM DRAM允许对同一行连续地访问可以直接从行缓冲区获得服务。
- 扩展输出DRAM(Extended Data Out DRAM,EDO DRAM)
FPM DRAM的一个增强形式,它允许单独的CAS信号在时间上靠的紧密一些。
- 同步DRAM(Synchronous DRAM,SDRAM)
就它们与存储控制器通信使用一组显示的控制信号来说,常规的,FPM,EDO都是异步的。
最终效果就是SDRAM能够比那些异步的存储器更快输出超单元内容。
- 双倍数据速率同步DRAM(Double Date-rate Synchronous DRAM,DDR SDRAM)
DDR SDRAM是对SDRAM的一种增强。
通过使用两个时钟沿作为控制信号,从而使DRAM 速度翻倍。。
- 视频RAM
主要用在图形系统的帧缓冲区中。
非易失性存储器
DRAM和SRAM断电后会丢失信息,是易失的。
由于历史原因,虽然ROM有的类型既可以读也可以写,但是它们整体被称为只读存储器(Read-Only Memory,ROM)。
- PROM (Programmable ROM,可编程ROM)
只能够被编程一次。PROM的每个存储器有一个熔丝,它只能用高电流熔断一次。
- 可擦写可编程ROM(Erasable Programmable ROM,EPROM)
EPROM能够被擦除和重编程的次数数量级可达1000次,电子可擦除PROM(EEPROM)类似EPROM,能编程的次数的数量级达到10^5级别
- 闪存(Flash Memory)
闪存也是一类非易失性存储器,基于EEPROM,已经成为一种重要的存储设备。
固态硬盘(Solid State Disk,SSD):基于闪存的磁盘驱动器。
固件(firework):存储在ROM设备中的程序,系统通电后会运行储存在ROM中的固件。
磁盘存储
从磁盘上读信息的时间为毫秒级,比从DRAM读慢了10w倍,比从SRAM读慢了100w倍。
磁盘构成
磁盘容量
对于与DRAM和SRAM容量相关的计量单位,1K=220,G=23,M=109。
磁盘操作
逻辑磁盘块
访问磁盘和连接I/O设备
固态硬盘
局部性
局部性分为时间局部性和空间局部性,在具有良好的时间局部性的程序中,被引用过的的一次内存位置很可能在不久的将来再次被引用,在具有良好的空间局部性的程序中,如果一个内存位置被引用过一次,那么程序很可能在不久的将来引用附近的一个内存位置。
for(int i=0;i<N;i++)
sum+=nums[i];/*对sum而言有良好时间局部性,对nums而言有良好空间局部性*/
像上面这样访问nums,成为步长为1的引用模式/顺序引用模式,一个连续向量中,每隔k个元素进行访问,称为步长为k的引用模式。一般而言,随步长增加,空间局部性下降。
存储器层次结构
速度较快的技术每字节的成本要比速度较慢的高,而且容量较小。对于一个典型的存储器层次结构,一般而言从底层往高层走,存储设备变得更大、更慢、更便宜。
缓存
高速缓存(cache) 是一个小而快速的存储设备。
缓存(caching):使用高速缓存的过程。
第k+1层存储器被划分为来纳许的数据对象组块,即块(block),块可以是固定大小也可以是可变大小,数据总以块的大小来传输数据。
- 缓存命中
程序需要第k+1层数据,在第k层中的块查找,若该数据刚好存在于第k层,则称为缓存命中。
- 缓存不命中
强制性不命中/冷不命中:即k层缓存为空,又叫冷缓存,这种不命中是短暂事件,不会重复执行。
冲突不命中:某些对象映射到同一个缓存块,导致缓存一直不命中。
容量不命中: 缓存太小而无法处理工作集。
一旦发生不命中,就要驱逐/替换某个牺牲块。
高速缓存存储器
S:2^s组数
E:每组的行数
B=2^b:块大小(字节)
m=log2(M):物理地址位数
C=SxExB:容量大小
抖动(thrash):高速缓存反复地加载和驱逐相同的高速缓存块的组。
采用中间的位做索引: 相邻的块能够映射到不同的高速缓存行中,使得对高速缓冲使用效率提高。
如何编写高速缓存友好的代码
- 让最常见的情况运行得快
将注意力放在核心函数的循环中
- 尽量减少每个循环内部的缓存不命中数量
1.对局部变量反复引用
2.使用步长为1的引用方式