内存屏障笔记

https://www.researchgate.net/publication/228824849_Memory_Barriers_a_Hardware_View_for_Software_Hackers

这是阅读这篇文章的一个笔记。该文从CPU乱序执行和缓存实现的角度来说明内存屏障是怎么回事。

CPU为了优化性能会同时执行多条指令,有可能后面的指令会先完成。CPU会保证从软件的角度来看,这个乱序执行和顺序执行的可观察结果是一样的,但这个仅仅限于单个CPU的情况。多个CPU的时候乱序执行有时不是程序想要的,而CPU没有相关的信息做判断,这时要软件通过内存屏障强制CPU做些特殊处理。

CPU(或者CPU核,下同)有自己的缓存(Cache)。但是也要让所有CPU看到一个一致的内存数据,各个CPU和内存控制器通过共享的总线运行一套协议(MESI协议及变种)来实现这点。

最主要的是当一个CPU要写数据的时候,发送Invalidate消息给所有的其他CPU,其他的CPU要回应InvalidateAck,当所有的InvalidateAck都收到的时候,就可以写数据到自己缓存。收到Invalidate的CPU要把这个数据从它自己的缓存中清除出去(如果有的话),这样当这个CPU要读取这个数据的时候,会发出Read消息,刚才写数据的CPU会响应最新的数据。

但是上面的协议有两个性能上的问题需要进行优化,而这两个优化导致需要内存屏障。

性能问题1 - 引入Store Buffer
写数据的CPU要等所有的InvalidateAck收到是需要等很久的,所以它先把数据暂时写到Store Buffer里面,然后继续执行后面的指令。在Store Buffer里面的数据别人都读不到,只有它自己能读。等收到所有InvalidateAck的时候再把Store Buffer里面的数据更新到自己缓存,此时别的CPU才能通过Read消息读到。
有了Store Buffer之后下面的程序会表现奇怪:

1 void foo(void)
2 {
3    a = 1;
4    b = 1;
5 }
6
7 void bar(void)
8 {
9    while (b == 0) continue;
10   assert(a == 1);
11 }

设想CPU1执行foo,CPU2执行bar。CPU1执行完成b=1的时候(InvalidateAck b都收到),a=1的执行结果还在Store Buffer中(有的InvalidateAck还没有收到)。而CPU2此时没有收到Invalidate a(或者收到Invalidate a,但还没有从缓存清除数据a),那么第10行的assert会fail。CPU2先观察到b==1,退出循环,但是此时第10行读取的a还是初始值0。
解决方法是在a=1和b=1之间插入写内存屏障smp_wmb,其作用就是等待内存屏障之前Store Buffer清空,就是说所有的InvalidateAck a要收到,然后把a的最新值写到缓存。而CPU2执行assert的时候必然已经发过InvalidateAck,再读取a时需要时发送Read请求,CPU1可以响应a的最新值1。

性能问题2 - 引入Invalidate Queue
CPU在收到Invalidate后,要把数据从缓存中清除出去这个操作很慢,从而发送InvalidateAck延迟,进而导致写操作变慢。解决方法是收到Invalidate之后立刻回应InvalidateAck,但是把Invalidate请求放在Invalidate Queue中慢慢处理。这时候上面的程序还有问题(尽管加了写内存屏障),尽管在执行第10行的时候,CPU2收到了Invalidate a,但是还放在队列中没有执行,读到的还是之前缓存的初始值0。解决方法是在第9行和第10行之间插入读内存屏障smp_rmb,其作用是执行所有Invalidate Queue中的请求,从而对于smp_rmb之后的读取时总是要发出Read请求,从而得到最新的数据。程序中执行第10行的时候,数据a已经从缓存清除出去了,再次发出Read时读取到最新值1。

1 void foo(void)
2 {
3   a = 1;
4   smp_wmb();
5   b = 1;
6 }
7
8 void bar(void)
9 {
10  while (b == 0) continue;
11  smp_rmb();
12  assert(a == 1);
13 }
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 参考 背景 在阅读java中volatile的关键词语义时,发现很多书中都使用了重排序这个词来描述,同时又讲到了线...
    iGroove阅读 1,753评论 0 3
  • 目录:1.数据依赖性2.程序顺序规则3.重排序对多线程的影响4.编译器重排序5.指令集并行的重排序6.内存系统的重...
    西部小笼包阅读 16,497评论 9 25
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,519评论 8 265
  • 写这篇博文主要源于几个月前的一条微博,大概讲的是“在一些数据结构中,需要修改某个数据,对整个数据结构加锁实现,然而...
    fooboo阅读 2,255评论 -3 0
  • 先提出个问题:您考虑过养老的问题吗,你觉得在中国社会养老会不会是个问题? 我们先看一组数据一一如果一个国家60岁年...
    金虞阅读 517评论 0 1