1. CPU缓存
进行服务器端开发的时候,我们会想到添加redis或memcache缓存,来减轻数据库的压力。对于CPU来说,内存实在是太慢了,每次读取数据都要进行长时间的等待,浪费了CPU资源。引入CPU缓存,95%的情况下,CPU只需要访问L1-L3 CPU缓存,而无需访问内存,大大提高了速度。
2. 直接映射缓存
直接映射缓存使用mod运算,确保任何一个内存块的地址,始终映射到一个固定的 CPU 缓存行地址。这里取模运算使用位运算,通常缓存行的数量为2的N次方,这样内存块的低N位就为对应的缓存行地址。
低三位用来映射缓存行,高二位则用来区分各个内存块,称为组标记。所以我们通过索引和组标记就能对应到具体的内存块在缓存行中的映射。通过偏移量来读取实际数据。
在访问内存地址的时候,我们首先会根据低位,找到缓存行。之后判断有效位,如果无效的话,访问内存并更新缓存。如果有效,对比内存地址高位和组标记是否相同,确认当前缓存行中的数据是否是我们要访问的内存块,如果不是,访问内存并更新缓存。如果是,根据偏移量找到目标数据。
3. 缓存一致性
目前的CPU都是多核心的,每个核心都有自己的L1、L2缓存,当多个CPU同时操作同一份数据,就会出现缓存不一致的问题。
解决这个问题,要保证两点:
- 写传播:一个CPU缓存更新,必须传播到其他CPU对应的缓存中
- 事务串行化:数据读写的顺序在每个核心看来,都是相同的
多核心之间数据传播最常见的方案,叫做总线嗅探,就是通过总线将读写请求广播给所有CPU核心,各个核心嗅探到请求根据自己的情况做响应。总线嗅探机制分为两种协议,一种是写失效协议,一个核心写入缓存后,会广播一个失效请求,其他核心嗅探到则将自己缓存中对应的缓存行标记为失效。另一种是写广播协议,写入缓存的核心除了要广播一个失效请求外,还要广播数据内容,把对应数据传输给其他CPU。
4. MESI协议
MESI协议是一个写失效的协议。他对于缓存行有四种标记:
- M:代表已修改(Modified)
- E:代表独占(Exclusive)
- S:代表共享(Shared)
- I:代表已失效(Invalidated)
已修改表示缓存中数据已经修改,但是还没有写回内存。已失效表示缓存行中的数据已经被修改过了,当前的数据是旧值,不可相信。
独占状态表示,只有当前核心加载了数据,所以如果要写入数据不需要通知其他核心。在独占状态收到一个来自总线的读取对应缓存的请求,他就会变成共享状态。当要修改共享状态的数据时,就要先向所有核心广播一个请求,将其他核心缓存的状态修改为失效,然后再写入数据到缓存中。这个广播操作类似于获取一个锁,一个对应当前缓存行数据的锁。