此时第一步处理器将变量S的更新后的数据写入到写缓冲器返回,接着马上执行了第二布进行S变量的读取。由于此时处理器对S变量的更新结果还停留在写缓冲器中,因此从高速缓存缓存行中读到的数据还是变量S的旧值。
为了解决这种问题,存储转发(Store Fowarding)这个概念上线了。其理论就是处理器在执行读操作时会先根据相应的内存地址从写缓冲器中查询。如果查到了直接返回,否则处理器才会从高速缓存中查找,这种从缓冲器中读取的技术就叫做存储转发。看图:
内存重排序和可见性的问题由于写缓冲器和无效化队列的出现,处理器的执行都变成了异步操作。缓冲器是每个处理器私有的,一个处理器所存储的内容是无法被其他处理器读取的。
举个例子:
CPU1 更新变量到缓冲器中,而CPU2因为无法读取到CPU1缓冲器内容所以从高速缓存中读取的仍然是该变量旧值。
其实这就是写缓冲器导致StoreLoad重排序问题,而写缓冲器还会导致StoreStore重排序问题等。
为了使一个处理器上运行的线程对共享变量所做的更新被其他处理器上运行的线程读到,我们必须将写缓冲器的内容写到其他处理器的高速缓存上,从而使在缓存一致性协议作用下此次更新可以被其他处理器读取到。
处理器在写缓冲器满、I/O指令被执行时会将写缓冲器中的内容写入高速缓存中。但从变量更新角度来看,处理器本身无法保障这种更新的”及时“性。为了保证处理器对共享变量的更新可被其他处理器同步,编译器等底层系统借助一类称为内存屏障的特殊指令来实现。
内存屏障中的存储屏障(Store Barrier)会使执行该指令的处理器将写缓冲器内容写入高速缓存。
内存屏障中的加载屏障(Load Barrier)会根据无效化队列内容指定的内存地址,将相应处理器上的高速缓存中相应的缓存条目状态标记为I。
四、内存屏障因为说了存储屏障(Store Barrier)和加载屏障(Load Barrier) ,所以这里再简单的提下内存屏障的概念。
划重点:(你细品)
处理器支持哪种内存重排序(LoadLoad重排序、LoadStore重排序、StoreStore重排序、StoreLoad重排序),就会提供相对应能够禁止重排序的指令,而这些指令就被称之为内存屏障(LoadLoad屏障、LoadStore屏障、StoreStore屏障、StoreLoad屏障)
划重点:
如果用X和Y来代替Load或Store,这类指令的作用就是禁止该指令左侧的任何 X 操作与该指令右侧的任何 Y 操作之间进行重排序(就是交换位置),确保指令左侧的所有 X 操作都优先于指令右侧的Y操作。
内存屏障的具体作用:
屏障名称 示例 具体作用StoreLoad Store1;Store2;Store3;StoreLoad;Load1;Load2;Load3 禁止StoreLoad重排序,确保屏障之前任何一个写(如Store2)的结果都会在屏障后任意一个读操作(如Load1)加载之前被写入
StoreStore Store1;Store2;Store3;StoreStore;Store4;Store5;Store6 禁止StoreStore重排序,确保屏障之前任何一个写(如Store1)的结果都会在屏障后任意一个写操作(如Store4)之前被写入
LoadLoad Load1;Load2;Load3;LoadLoad;Load4;Load5;Load6 禁止LoadLoad重排序,确保屏障之前任何一个读(如Load1)的数据都会在屏障后任意一个读操作(如Load4)之前被加载
LoadStore Load1;Load2;Load3;LoadStore;Store1;Store2;Store3 禁止LoadStore重排序,确保屏障之前任何一个读(如Load1)的数据都会在屏障后任意一个写操作(如Store1)的结果被写入高速缓存(或主内存)前被加载
五、总结
其实从头看到尾就会发现,一个技术点的出现往往是为了填补另一个的坑。
为了解决处理器与主内存之间的速度鸿沟,引入了高速缓存,却又导致了缓存一致性问题
为了解决缓存一致性问题,引入了如MESI等技术,又导致了处理器等待问题
为了解决处理器等待问题,引入了写缓冲和无效化队列,又导致了重排序和可见性问题
为了解决重排序和可见性问题,引入了内存屏障,舒坦。。。