架构
冯诺依曼模型
- 运算器(
ALU
)、控制器、存储器、输入设备、输出设备 - 也称普林斯顿结构,是一种将程序指令存储器和数据存储器合并在一起的存储器结构。
哈佛架构
- 将程序指令存储和数据存储分开的存储器结构。
ARM9
、ARM10
和ARM11
,51
单片机属于哈佛结构。
Cache
缓存(Cache
)在计算中是一个高速数据存储层,它存储一部分暂时的数据,使得未来对这些数据的请求能够比直接访问数据的主要存储位置更快。缓存允许高效地重复使用之前检索或计算的数据 cache。
L1 Cache
通常分成「数据缓存」和「指令缓存」
对于存储器,它的速度越快、能耗会越高、而且材料的成本也是越贵的,以至于速度快的存储器的容量都比较小。
寄存器的访问速度非常快,一般要求在半个 CPU
时钟周期内完成读写,CPU
时钟周期跟 CPU
主频息息相关,比如 2 GHz
主频的 CPU
,那么它的时钟周期就是 1/2G
,也就是 0.5ns
(纳秒)。
CPU Cache 用的是一种叫 SRAM(Static Random-Access Memory,静态随机存储器) 的芯片。
每个 CPU 核心都有一块属于自己的 L1
高速缓存,指令和数据在 L1 是分开存放的,所以 L1 高速缓存通常分成指令缓存和数据缓存。
内存用的芯片和 CPU Cache
有所不同,它使用的是一种叫作 DRAM (Dynamic Random Access Memory,动态随机存取存储器) 的芯片。
DRAM
存储一个 bit
数据,只需要一个晶体管和一个电容就能存储,但是因为数据会被存储在电容里,电容会不断漏电,所以需要定时刷新电容,才能保证数据不会被丢失,这就是 DRAM 之所以被称为动态存储器的原因
存储层次结构也形成了缓存的体系
L1 Cache 通常会分为「数据缓存」和「指令缓存」
index0 也就是数据缓存,而 index1 则是指令缓存,它两的大小通常是一样的。
L3 Cache 是多个 CPU 核心共享的。
Cache Line(缓存块)
直接映射 Cache
内存块(Block)
CPU Line 中我们还会存储一个组标记(Tag)
实际存放数据(Data)
有效位(Valid bit)
如果有效位是 0,无论 CPU Line 中是否有数据,CPU 都会直接访问内存,重新加载数据。
字(Word
)
[!一个内存的访问地址 ] 组标记 CPU Line 索引 偏移量
索引 + 有效位 + 组标记 + 数据块组成
对比内存地址中组标记和 CPU Line 中的组标记,确认 CPU Line 中的数据是我们要访问的内存数据
Cache分类
- 全相连 Cache
- 组相连 Cache
- CPU Cache 一次性能加载数据的大小,可以在 Linux 里通过
coherency_line_size
配置查看 它的大小,通常是 64 个字节。
分支预测
分支预测器会动态地根据历史命中数据对未来进行预测
如果一个进程在不同核心来回切换,各个核心的缓存命中率就会受到影响
当有多个同时执行「计算密集型」的线程,为了防止因为切换到不同的核心,而导致缓存命中率下降的问题,我们可以把线程绑定在某一个 CPU 核心上,
内存与Cache的一致性
写直达
保持内存与 Cache 一致性最简单的方式是,把数据同时写入内存和 Cache 中,这种方法称为写直达(Write Through)。
如果数据已经在 Cache 里面,先将数据更新到 Cache 里面,再写入到内存里面;如果数据没有在 Cache 里面,就直接把数据更新到内存里面。写直达法很直观,也很简单,但是问题明显,无论数据在不在 Cache 里面,每次写操作都会写回到内存
写回
在写回机制中,当发生写操作时,新的数据仅仅被写入 Cache Block 里,只有当修改过的 Cache Block「被替换」时才需要写到内存中
那具体如何做到的呢?下面来详细说一下:
如果当发生写操作时,数据已经在CPU Cache
里的话,则把数据更新到 CPU Cache
里,同时标记CPU Cache
里的这个 Cache Block
为脏(Dirty) 的,这个脏的标记代表这个时候,我们 CPU Cache
里面的这个 Cache Block
的数据和内存是不一致的,这种情况是不用把数据写到内存里的;
如果当发生写操作时,数据所对应的 Cache Block 里存放的是「别的内存地址的数据」的话,就要检查这个 Cache Block 里的数据有没有被标记为脏的,如果是脏的话,我们就要把这个 Cache Block 里的数据写回到内存,然后再把当前要写入的数据,写入到这个 Cache Block 里,同时也把它标记为脏的;如果 Cache Block 里面的数据没有被标记为脏,则就直接将数据写入到这个 Cache Block 里,然后再把这个 Cache Block 标记为脏的就好了。
这个脏的标记代表这个时候,我们 CPU Cache 里面的这个 Cache Block 的数据和内存是不一致的
如果当发生写操作时,数据所对应的 Cache Block 里存放的是「别的内存地址的数据」的话,就要检查这个 Cache Block 里的数据有没有被标记为脏的,如果是脏的话,我们就要把这个 Cache Block 里的数据写回到内存,然后再把当前要写入的数据,写入到这个 Cache Block 里,同时也把它标记为脏
这个就是所谓的缓存一致性问题,A 号核心和 B 号核心的缓存,在这个时候是不一致,从而会导致执行结果的错误。
写传播
某个 CPU 核心里的 Cache
数据更新时,必须要传播到其他核心的 Cache
。称之为写传播(Wreite Propagation)
某个 CPU 核心里对数据的操作顺序,必须在其他核心看起来顺序是一样的,这个称为事务的串行化(Transaction Serialization)
CPU
核心对于 Cache
中数据的操作,需要同步给其他 CPU
核心;
要引入锁的概念,如果两个 CPU
核心里有相同数据的 Cache
,那么对于这个 Cache
数据的更新,只有拿到了锁,才能进行对应的数据更新。
总线嗅探(BusSnooping)
MESI 协议
总结
Cache 伪共享
避免 Cache 伪共享实际上是用空间换时间的思想,浪费一部分 Cache
空间,从而换来性能的提升。
CPU Line
是 CPU
从内存读取数据到 Cache
的单位,一般的大小为64
字节。当多个变量存储在同一个缓存行中时,任何一个变量的修改都会导致整个缓存行失效,从而影响其他变量的性能。
指令
- 指令中的立即数—常数
- 编译程序—构造指令(指令编码)
CPU
执行程序—解析指令(指令解码)
PC指针-程序计数器
用来存储 CPU 要执行下一条指令所在的内存地址。
不是存储了下一条要执行的指令,此时指令还在内存中,程序计数器只是存储了下一条指令「的地址」。
寻址空降
64
位Linux一般使用48
位表示虚拟空间地址,40
位标识物理地址。32
位Linux
系统的寻址空间为4GB(2^32
)- 用户进程可用地址空间受限于内核映射及其他系统资源
- 通常用户进程可用地址空间在2GB至3GB之间
PAE
技术可扩展32
位系统物理内存范围,但单个进程仍受32
位寻址限制