【发布时间】:2015-02-13 00:23:55
【问题描述】:
大多数 CPU 架构都会重新排序存储-加载操作,但我的问题是为什么?我对存储负载障碍的解释如下:
x = 50;
store_load_barrier;
y = z;
此外,与释放和获取语义相比,我看不出这个障碍在无锁编程中有多大用处。
【问题讨论】:
标签: multithreading concurrency cpu-architecture lock-free
大多数 CPU 架构都会重新排序存储-加载操作,但我的问题是为什么?我对存储负载障碍的解释如下:
x = 50;
store_load_barrier;
y = z;
此外,与释放和获取语义相比,我看不出这个障碍在无锁编程中有多大用处。
【问题讨论】:
标签: multithreading concurrency cpu-architecture lock-free
简短回答:存储加载屏障可防止处理器推测性地执行存储加载屏障之后的 LOAD,直到所有先前的存储都完成为止。
详情:
存储加载屏障昂贵的原因是它阻止了跨屏障的 LOAD 和 STORE 操作的重新排序。
假设您有如下指令序列:
... ;; long latency operation to compute r1
ST r1, [ADDR1] ;; store value in r1 to memory location referenced by ADDR1
LD r3, [ADDR2] ;; load r3 with value in memory location ADDR2
... ;; instructions that use result in r3
当这个序列执行时,r1 的值将是一个需要很长时间才能完成的操作的结果。指令ST r1, [ADDR1] 将不得不停止直到r1 被读取。与此同时,如果LD r3, [ADDR2] 和其他指令独立于先前的存储,则乱序处理器可以推测性地执行它们。在提交存储之前,它们不会真正提交,但是通过推测性地完成大部分工作,结果可以保存在重新排序缓冲区中,并准备好更快地提交。
这适用于单处理器系统,因为 CPU 可以检查 ADDR1 和 ADDR2 之间是否存在依赖关系。但是在多处理器系统中,多个 CPU 可以独立执行加载和存储。可能有多个处理器对 ADDR1 执行 ST 并从 ADDR2 执行 LD。如果 CPU 能够推测性地执行这些似乎没有依赖关系的指令,那么不同的 CPU 可能会看到不同的结果。我认为following blog post 很好地解释了这是如何发生的(我认为我不能在这个答案中简明扼要地总结一下)。
现在考虑具有存储加载屏障的代码序列:
... ;; long latency operation to compute r1
ST r1, [ADDR1] ;; store value in r1 to memory location referenced by ADDR1
ST_LD_BARRIER ;; store-load barrier
LD r3, [ADDR2] ;; load r3 with value in memory location ADDR2
... ;; instructions that use result in r3
这将防止LD r3, [ADDR2] 指令和后续相关指令被推测执行,直到之前的存储指令完成。这可能会降低 CPU 性能,因为整个 CPU 流水线可能不得不在等待 ST 指令完成时停止,即使在 CPU 本身中,LD 和 ST 之间没有依赖关系。
可以采取一些措施来限制 CPU 必须停止的数量。但最重要的是,存储-加载屏障会在加载和存储之间产生额外的依赖关系,这会限制 CPU 可以执行的推测执行量。
【讨论】: