系列文章目录
这是本周期内系列打卡文章的所有文章的目录
- 《Go 并发数据结构和算法实践》学习笔记 Day 1
- 《Go 并发数据结构和算法实践》学习笔记 Day 2
- 《说透芯片》学习笔记 Day 3
- 《深入浅出计算机组成原理》学习笔记 Day 4
文章目录
- 系列文章目录
- 前言
- 一、概念理解(What)
- 二、内存屏障,解决方案发展的脉络(How、Why)
- 三、延伸学习
- 总结
前言
提示:这里可以添加本文要记录的大概内容:
学习内容:https://time.geekbang.org/column/article/462113
知识主客体建立关系:
之前研究DPDK无锁队列,看到实现中有些读屏障和写屏障,只是模糊理解个大概,但不知道它在什么时候出问题,所有用屏障保证时序正确性。现在看了这篇文章,理解了读写屏障解决store buffer引入的问题,读屏障解决invalid queue问题,两个异步化设计提升性能之后,要兼容Cache机制协议,保证程序在多核上时序正确。
提示:以下是本篇文章正文内容,下面案例可供参考
一、概念理解(What)
这点文章解释得很清楚,我整理一下从文章中学到的概念。
- buffer和cache的区别:打个比喻,cache 往往意味着它所存储的信息是副本,但是 buffer 更像是蓄水池,buffer 中的数据没有副本,一旦丢失就彻底丢失了。两者都是结构性的优化,cache 存在的意义是加速查找。
- store buffer 作为buffer结构引入的设计,是硬件实现的缓冲区,自然它的读写速度比缓存的速度更快,看下图就懂了。先搜集多次写操作,然后在合适的时机进行提交,作为CPU私有,同样需要兼容对应的缓存协议。
store buffer 的存在是为提升写性能,放弃了缓存的顺序一致性,我们把这种现象称为弱缓存一致性。
-
内存屏障,屏障的作用是前边的读写操作未完成的情况下,后面的读写操作不能发生。如Arm 上 dmb 指令。
-
失效队列:
由于 store buffer 的存在提升了写入速度,那么 invalid 消息确认速度相比起来就慢了,这就带来了速度的不匹配,很容易导致 store buffer 的内容还没有及时更新到 cache 里,自己的容量就被撑爆了,从而失去了加速的作用。为了解决这个问题,CPU 设计者又引入了“invalid queue”
如下图:
5. 读写屏障分离
分离的写屏障和读屏障的出现,是为了更加精细地控制 store buffer 和 invalid queue 的顺序。
- 单向屏障
(half-way barrier) 也是一种内存屏障,但它并不是以读写来区分的,而是像单行道一样,只允许单向通行,例如 Arm 中的 stlr 和 ldar 指令就是这样。
stlr 的全称是 store release register,也就是以 release 语义将寄存器的值写入内存;ldar 的全称是 load acquire register,也就是以 acquire 语义从内存中将值加载入寄存器。我们重点就来看看 release 和 acquire 语义。
语义 | 对比 |
---|---|
release 语义 | 挡前不挡后 |
acquire 语义 | 挡后不挡前 |
二、内存屏障,解决方案发展的脉络(How、Why)
- 用 store buffer 也会有一个问题,那就是它并不能保证变量写入缓存和主存的顺序,这也就需要引入内存屏障。
一个代码示例,可以用GEM5 CPU仿真做成验证实验:
// CPU0
void foo() {
a = 1;
b = 1;
}
// CPU1
void bar() {
while (b == 0) continue;
assert(a == 1);
}
在这个代码块中,CPU0 执行 foo 函数,CPU1 执行 bar 函数。但在对变量 a 和 b
进行赋值的时候,有两种情况会导致它们的赋值顺序被打乱。
第一种情况是 CPU 的乱序执行。 CPU 为了提升运行效率和提高缓存命中率,采用了乱序执行。
第二种情况是 store buffer 在写入时,有可能 b 所对应的缓存行会先于 a
所对应的缓存行进入独占状态,也就是说 b 会先写入缓存。
- 由于 store buffer 的存在提升了写入速度,那么 invalid 消息确认速度相比起来就慢了,这就带来了速度的不匹配,很容易导致 store buffer 的内容还没有及时更新到 cache 里,自己的容量就被撑爆了,从而失去了加速的作用。为了解决这个问题,CPU 设计者又引入了**“invalid queue”**
极客时间版权所有: https://time.geekbang.org/column/article/462113
3. 分离的写屏障和读屏障的出现,是为了更加精细地控制 store buffer 和 invalid queue 的顺序。
三、延伸学习
内存屏障是硬件篇,其中不同体系结构CPU的设计,都有各有特色的方案及取舍。可以延伸的点也很多:
- X86的模型:为啥X86 采用的 TSO 模型不存在缓存一致性的问题
- ARM的CPU设计:通过单向通行来区分屏障
- alpha体系结构:通过读写来区分屏障
- 语言层面,如Java的Unsafe对内存屏障的抽象和支持
- 从MESI延伸出弱缓存一致性协议,具体协议细节等
- 内存屏障生产方案的使用案例:DPDK无锁队列实现等
总结
提示:这里对文章进行总结:
内在脉络,软件开发随着硬件设计而发展:
CPU 从单核发展为多核,增加缓存,导致出现了多个核间的缓存一致性问题 --> 为了解决缓存一致性问题,提出了 MESI 协议 --> 完全遵守 MESI 又会给 CPU 带来性能问题 --> CPU 设计者又增加 store buffer 和 invalid queue --> 又导致了缓存的顺序一致性变为了弱缓存一致性 --> 需要缓存的顺序一致性的,就需要软件工程师自己在合适的地方添加内存屏障。
内容来源:
极客时间:16 | 编程高手必学的内存知识