use*_*528 2 x86 multithreading cpu-architecture low-level lock-free
假设2个内核试图在相同的时刻(正负eta)将不同的值写入相同的RAM地址(1个字节),并且不使用任何互锁指令或内存屏障.在这种情况下会发生什么以及将什么值写入主RAM?第一个获胜?最后一个获胜?不确定的行为?
x86(与所有其他主流SMP CPU架构一样)具有连贯的数据缓存.两个差异缓存(例如,2个不同核心的L1D)不可能为同一缓存行保存冲突数据.
硬件强制执行一个命令(通过一些特定于实现的机制来断开关系,以防两个所有权请求从不同的核心到达相同的时钟周期).在大多数现代x86 CPU中,第一个存储不会写入RAM,因为有一个共享的回写L3缓存来吸收一致性流量,而无需往返内存.
在全局订单中的两个商店之后出现的加载将看到由第二个商店存储的值.
(我假设我们正在讨论普通(非NT)存储到可缓存的内存区域(WB,而不是USWC,UC,甚至WT).但在任何一种情况下,基本思想都是相同的; 一个商店会去首先,下一步将踩到它.如果在全局顺序中它们之间发生负载,则可以临时观察来自第一个商店的数据,但是否则硬件选择做第二个的商店中的数据将是长的 - 效果.
我们讨论的是单个字节,因此存储不能分割成两个缓存行,因此每个地址都是自然对齐的,所以为什么在x86上自然对齐的变量原子上的整数赋值?适用.
通过要求核心在其可以修改之前获得对该高速缓存行的独占访问来维持一致性(即,通过将其从存储队列提交到L1D高速缓存来使存储全局可见).
这种"获取独占访问"的东西是使用MESI协议的(变体)完成的.高速缓存中的任何给定行都可以是修改(脏),独占(尚未写入),共享(干净副本;其他高速缓存也可能有副本,因此在写入之前需要RFO(读取/请求所有权)),或者无效.MESIF(英特尔)/ MOESI(AMD)添加额外状态以优化协议,但不改变基本逻辑,即任何时候只有一个核心可以改变一条线路.
如果我们关心对两条不同线路进行多次更改的排序,那么内存排序的内存就会起作用.但是,当商店在同一时钟周期内执行或退出时,关于"哪个商店获胜"的问题,这一点都不重要.
存储执行时,它会进入存储队列.它可以承诺L1D并在退休后随时变得全球可见,但不是之前; 未退出的指令被视为推测性的,因此它们的架构效果必须在CPU核心之外不可见.投机载荷没有建筑效应,只有微架构1.
因此,如果两个存储都准备好"同时"提交(时钟不一定在核心之间同步),那么其中一个将首先成功获得RFO并获得独占访问权限,并使其存储数据全局可见.然后,不久之后,另一个核心的RFO将成功并使用其数据更新缓存行,因此其存储在所有其他核心观察到的全局存储顺序中排名第二.
x86具有总存储顺序内存模型,即使存储到不同的缓存行,所有内核都遵循相同的顺序(除了始终按程序顺序查看自己的存储).像PowerPC这样的弱有序体系结构会允许某些内核看到与其他内核不同的总顺序,但这种重新排序只能在不同行的存储之间进行.单个缓存行始终有一个修改顺序.(相对于彼此和其他存储的负载重新排序意味着您必须小心如何在弱有序的ISA上观察事物,但是MESI强加的缓存行有一个修改顺序).
哪一个赢得比赛可能取决于环形总线上的核心布局相对于线路映射到哪个共享L3缓存片段的平淡无奇的东西.(注意使用"race"这个词:这是"竞争条件"错误所描述的那种竞赛.编写代码并不总是错误的,其中两个不同步的商店更新同一个位置并且你不关心哪个获胜,但这很罕见.)
顺便说一下,现代x86 CPU在多个内核争用原子读 - 修改 - 写入同一个高速缓存行(并因此保持多个时钟周期来制作lock add byte [rdi], 1原子)的情况下具有硬件仲裁,但是只需要定期加载/存储为一个周期拥有一个缓存行来执行加载或提交商店.我认为locked指令的仲裁是一个不同的东西,当多个内核试图将存储提交到同一个缓存行时,核心会从中获胜.除非您使用pause指令,否则内核会假设其他内核未修改相同的高速缓存行,并且推测性地早期加载,因此如果确实发生了内存排序错误推测.(生产者 - 消费者共享超兄弟姐妹与非兄弟姐妹之间的内存位置的延迟和吞吐量成本是多少?)
IDK,如果两个线程都只是在没有加载的情况下存储时发生了类似的事情,但可能不是因为存储不是按推测性重新排序,而是与存储队列的无序执行分离.一旦商店指令退出,商店肯定会发生,所以OoO exec不必等待实际提交.(实际上它必须在它可以提交之前退出OoO核心,因为这就是CPU如何知道它是非推测的;即没有先前的指令出错或是错误预测的分支)
脚注:
| 归档时间: |
|
| 查看次数: |
295 次 |
| 最近记录: |