assembly - 为什么要刷新其他逻辑处理器引起的内存顺序违规的管道?

标签 assembly x86 cpu-architecture memory-barriers speculative-execution

Memory Order Machine Clear performance event is described by the vTune documentation为:

The memory ordering (MO) machine clear happens when a snoop request from another processor matches a source for a data operation in the pipeline. In this situation the pipeline is cleared before the loads and stores in progress are retired.



但是我不明白为什么会这样。在不同逻辑处理器上的加载和存储之间没有同步顺序。
处理器可以假装在所有当前运行中的数据操作都提交后进行监听。

这个问题也被描述here

A memory ordering machine clear gets triggered whenever the CPU core detects a “memory ordering conflict”. Basically, this means that some of the currently pending instructions tried to access memory that we just found out some other CPU core wrote to in the meantime. Since these instructions are still flagged as pending while the “this memory just got written” event means some other core successfully finished a write, the pending instructions – and everything that depends on their results – are, retroactively, incorrect: when we started executing these instructions, we were using a version of the memory contents that is now out of date. So we need to throw all that work out and do it over. That’s the machine clear.



但这对我来说没有任何意义,CPU不需要重新执行“加载队列”中的加载,因为没有针对非锁定加载/存储的总订单。

我可以看到一个问题,即允许对负载进行重新排序:
;foo is 0
mov eax, [foo]    ;inst 1
mov ebx, [foo]    ;inst 2
mov ecx, [foo]    ;inst 3

如果执行顺序为1 3 2,则mov [foo], 1之类的存储在3到2之间将导致
eax = 0
ebx = 1
ecx = 0

这确实违反了内存排序规则。

但是负载不能随负载重新排序,那么当来自另一个内核的监听请求与任何进行中的负载的来源相匹配时,为什么英特尔的CPU会刷新管道?
此行为可以防止什么错误情况?

最佳答案

尽管x86内存排序模型不允许对WC以外的任何其他内存类型的加载在程序顺序之外进行全局观察,但该实现实际上允许加载按顺序完成。在所有先前的负载都已完成之前,暂停发出负载请求会非常昂贵。考虑以下示例:

load X
load Y
load Z

假定第x行不存在于缓存层次结构中,而必须从内存中获取。但是,Y和Z都存在于L1高速缓存中。满足x86负载排序要求的一种方法是在负载X获得数据之前不发出负载Y和X。但是,这将使所有依赖Y和Z的指令停顿,从而可能会严重打击性能。

在文献中已经提出并广泛研究了多种解决方案。英特尔已在其所有处理器中实现的功能是允许按顺序发出负载,然后检查是否发生了内存排序冲突,在这种情况下,将重新发出违反的负载,并重播其所有相关指令。但是,只有在满足以下条件时,才会发生这种违规:
  • 加载已完成,而程序顺序中的上一个加载仍在等待其数据,并且这两个加载是需要排序的内存类型。
  • 另一个物理或逻辑内核已修改了后来的加载读取的行,并且在较早的加载获取数据之前,发出加载的逻辑内核已检测到此更改。

  • 当这两种情况同时发生时,逻辑核心将检测到内存排序冲突。考虑以下示例:
    ------           ------
    core1            core2
    ------           ------
    load rdx, [X]    store [Y], 1
    load rbx, [Y]    store [X], 2
    add  rdx, rbx
    call printf
    

    假设初始状态为:
  • [X] = [Y] =0。
  • 在core1的L1D中已经存在包含Y的缓存行。但是X不在core1的专用缓存中。
  • 线X以可更改的相干状态存在于core2的L1D中,而线Y以可共享的状态存在于core2的L1D中。

  • 根据x86强排序模型,唯一可能的合法结果是0、1和3。特别是,结果2是不合法的。

    可能会发生以下事件序列:
  • Core2为这两条线都发出RFO。 X行的RFO将很快完成,但是Y行的RFO必须一直到L3,以使core1的专用缓存中的行无效。请注意,core2只能按顺序提交存储,因此,到第X行的存储要等到提交到Y行的存储为止。
  • Core1将两个负载发布到L1D。 Y行的加载很快完成,但是X行的加载需要从core2的专用缓存中获取该行。请注意,此时的Y值为零。
  • 第Y行从core1的专用缓存中无效,并且其在core2中的状态更改为可修改的一致性状态。
  • Core2现在按顺序提交两个存储。
  • 第X行从core2转发到core1。
  • Core1从高速缓存行X加载core2存储的值为2。
  • Core1打印X和Y的总和,即0 + 2 =2。这是非法结果。本质上,core1已加载了陈旧值Y。

  • 为了维持加载的顺序,core1的加载缓冲区必须将所有失效监听到驻留在其专用缓存中的行。当它检测到Y行已经无效时,在程序顺序中从无效行开始的未完成加载之前有未完成的加载时,就会发生内存排序冲突,必须重新发出该加载,之后它才能获得最新的值。请注意,如果在使其无效和从X的加载完成之前,已将行Y从core1的专用缓存中逐出,则它可能首先无法监听行Y的无效。因此,还需要一种机制来处理这种情况。

    如果core1从不使用加载的值中的一个或两个,则可能会发生加载顺序冲突,但永远无法观察到。类似地,如果core2存储到行X和Y的值相同,则可能会发生负载排序冲突,但无法观察到。但是,即使在这些情况下,core1仍将不必要地重新发出违反的负载并重播其所有依赖项。

    关于assembly - 为什么要刷新其他逻辑处理器引起的内存顺序违规的管道?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55563077/

    相关文章:

    c - 使用缓冲区溢出执行shell代码

    c - 以下是VS2012的一些汇编代码,我如何在gcc上编写它?

    assembly - NASM 程序集 : Can't find valid values for all labels after 1004 passes

    assembly - 为什么 vhaddps 指令会以如此复杂的方式添加?

    caching - 透视与旁观

    x86 - 英特尔 Nehalem 微架构可以实现的最大 IPC 是多少?

    cpu - 如何判断我的计算机是哈佛架构还是冯诺依曼架构?

    c++ - 关于 ADC,-1 (0xFFFFFFFF) 有什么特别之处吗?

    embedded - BIOS ROM 如何映射到 PC 上的地址空间?

    c++ - 操作系统如何检测进程崩溃