在计算机中,cpu和内存的交互最为频繁,相比内存,磁盘读写太慢,内存相当于高速的缓冲区。
但是随着cpu的发展,内存的读写速度也远远赶不上cpu,这样在处理器时钟周期内,cpu常常需要等待主存,浪费资源。。因此cpu厂商在每颗cpu上加上高速缓存,用于缓解这种情况。
CPU在读取数据时, 先在L1中寻找, 再从L2中寻找, 再从L3中寻找, 然后是内存, 最后是外存储器
缓存的意义
- 时间局部性:如果某个数据被访问,那么在不久的将来它很可能被再次访问;
- 空间局部性:如果某个数据被访问,那么与它相邻的数据很快也可能被访问;
带有高速缓存的CPU执行计算的流程
- 程序以及数据被加载到主内存
- 指令和数据被加载到CPU的高速缓存
- CPU执行指令,把结果写到高速缓存
- 高速缓存中的数据写回主内存
缓存一致性
通过高速缓存的存储交互很好的解决了处理器与内存的速度矛盾,但是也为计算机系统带来了更高的复杂度,因为它引入了一个新的问题,缓存一致性。
同一份数据可能会被缓存到多个 CPU 中,如果在不同 CPU 中运行的不同线程看到同一份内存的缓存值不一样就会存在缓存不一致的问题。为了达到数据访问的一致,需要各个处理器在访问缓存时遵循一些协议,在读写时根据协议来操作,不同的系统架构有不同的处理方式,常见的协议有MSI,MESI,MOSI 等。
排序缓冲区
MESI协议虽然可以实现缓存的一致性,但也会存在一些问题
CPU在cache line状态的转化期间是阻塞的,经过长时间的优化,在寄存器和L1缓存之间添加了LoadBuffer、StoreBuffer来降低阻塞时间,LoadBuffer、StoreBuffer,合称排序缓冲。Buffer与一级缓存进行数据传输时,CPU无须等待。
CPU执行load读数据时,把读请求放到LoadBuffer,这样就不用等待其它CPU响应,先进行下面操作,稍后再处理这个读请求的结果。
CPU执行store写数据时,把数据写到StoreBuffer中,待到某个适合的时间点,把StoreBuffer的数据刷到主存中。
因为StoreBuffer的存在,CPU在写数据时,真实数据并不会立即表现到内存中,所以对于其它CPU是不可见的;同样的道理,LoadBuffer中的请求也无法拿到其它CPU设置的最新数据;由于StoreBuffer和LoadBuffer是异步执行的,所以在外面看来,先写后读,还是先读后写,没有严格的固定顺序。
CPU通过store buffer
和invalid queue
(用来实现LoadBuffer)来降低延时。当在invalid状态进行写入时,首先会给其它CPU核发送invalid消息,然后把当前写入的数据写入到store buffer中。然后在某个时刻在真正的写入到cache line中。由于不是马上写入到cache line中,所以当前核如果要读cache line中的数据,需要先扫描store buffer,同时其它CPU核是看不到当前核store buffer中的数据的。除非store buffer中的数据被刷到cache中。对于invalid queue,当收到invalid消息时,cache line不会马上变成invalid状态,而是把消息写入invalid queue中。和store buffer不同的是当前核是无法扫描invalid queue的。为了保证数据的一致性,这就需要memory barrier了。store barrier会把store buffer中的数据刷到cache中,read barrier会执行invalid queue中的消息。