【问题标题】:What does "store-buffer forwarding" mean in the Intel developer's manual?英特尔开发人员手册中的“存储缓冲区转发”是什么意思?
【发布时间】:2014-08-02 07:37:43
【问题描述】:

Intel 64 and IA-32 Architectures Software Developer's Manual 表示 遵循单个处理器对操作的重新排序(第 8.2.2 节, “P6 和最新处理器系列中的内存排序”):

读取可能会随着旧写入到不同位置而重新排序,但 不是旧的写入相同的位置。

然后在下面讨论与之前相比放松的地方 处理器,它说:

存储缓冲区转发,当读取将写入传递到同一内存时 位置。

据我所知,“存储缓冲区转发”没有精确定义 任何地方(也不是“通过”)。读通过写是什么意思 到这里的同一个位置,因为上面说不能读取 重新排序并写入相同的位置?

【问题讨论】:

标签: assembly x86 intel cpu-architecture memory-model


【解决方案1】:

命名有点别扭。 “转发”发生在核心/逻辑处理器内部,如下所示。如果您首先执行 STORE,它将进入存储缓冲区以异步刷新到内存。如果您在将值刷新到缓存/内存之前对同一处理器上的同一位置执行后续加载,则存储缓冲区中的值将被“转发”,您将获得刚刚存储的值。读取正在“传递”写入,因为它发生在从存储缓冲区到内存的实际写入之前(尚未发生)。

如果您只关心排序规则,该声明实际上并没有说太多 - 这个转发是他们内部所做的事情的细节,以保证(在处理器上)读取不会与旧写入重新排序到同一位置(您引用的规则的一部分)。

尽管此处有其他一些答案,但(至少就订购保证而言)处理器/内核之间没有存储缓冲区转发/窥探,因为 8.2.3.5 “允许处理器内转发”手册中的示例显示。

【讨论】:

【解决方案2】:

我猜想挂断是“存储缓冲区”的概念。起点是处理器核心速度和内存速度之间的巨大差距。现代核心可以轻松地在一纳秒内执行十几条指令。但是一个 RAM 芯片可能需要 150 纳秒来传递一个存储在内存中的值。这是一个巨大的不匹配,现代处理器充满了解决这个问题的技巧。

读取是更难解决的问题,当处理器需要等待内存子系统传递值时,它会停止并且不执行任何代码。处理器中一个重要的子单元是预取器。它试图预测程序将加载哪些内存位置。所以它可以告诉内存子系统提前读取它们。因此,物理读取的发生比程序中的逻辑加载要快得多。

写入更容易,处理器为它们提供了一个缓冲区。将它们建模为软件中的队列。因此执行引擎可以快速地将存储指令转储到队列中,而不会陷入等待物理写入发生的困境。这是存储缓冲区。因此,对内存的物理写入比程序中的逻辑存储要晚得多。

当您的程序使用多个线程并且它们访问相同的内存位置时,问题就开始了。这些线程将在不同的内核上运行。许多问题与此有关,排序变得非常重要。显然,预取器执行的早期读取会导致它读取过时的值。存储缓冲区执行的延迟写入使情况变得更糟。解决它需要线程之间的同步。这是非常昂贵的,处理器很容易停止几十纳秒,等待内存子系统赶上。它们实际上可以使程序变慢,而不是线程使您的程序更快。

处理器可以提供帮助,存储缓冲区转发就是这样一种技巧。当存储仍在缓冲区中且尚未执行时,一个线程中的逻辑读取可以传递由另一个线程发起的物理写入。程序中如果没有同步,总是会导致线程读取一个过时的值。存储缓冲区转发所做的是查看缓冲区中的待处理存储,并找到与读取地址匹配的最新写入。这会及时“转发”存储,使其看起来比实际执行得更早。线程获取实际值;那个,最终,在记忆中结束。读取不再通过写入。

实际上编写一个利用存储缓冲区转发的程序是相当不可取的。从 非常 不确定的时间来看,这样的程序将非常非常糟糕地移植。英特尔处理器具有强大的内存模型以及它提供的订购保证。但你不能忽视如今在移动设备上流行的那种处理器。 提供这样的保证会消耗更少的能量。

而且该功能实际上可能非常有害,它隐藏了代码中的同步错误。它们是最难诊断的错误。在过去的 30 年中,微处理器取得了惊人的成功。然而,他们并没有变得更容易编程。

【讨论】:

  • 谢谢,这是对存储缓冲区转发的一个很好的解释。我想这里的重要部分是读取在 physical 写入之前通过,而不是程序顺序“逻辑”写入。澄清一下:写线程和读线程是在同一个内核上运行还是在不同内核上运行?也就是说,一个核心可以/是否可以窥探到另一个核心的存储缓冲区?如果您更新答案以解决该问题,我会将其标记为已接受。再次感谢!
  • 不同的内核,窥探是真正的 afaik。超线程和 NUMA 使故事复杂化,我对此了解不够。
  • @jacobsa - 不,无论如何在 x86 上,一个逻辑线程上的存储不能转发到来自同一核心上的另一个逻辑处理器的负载,因为它会违反 x86 内存模型。事实上,逻辑间核心共享是相当棘手的:一个线程上的存储将窥探另一个线程的负载缓冲区,如果有命中,你会得到一个“机器清除”,这基本上会破坏管道。这是为了避免另一个顺序冲突,因为线程共享一个 L1(所以 MESI 不在图片中,您需要另一种机制)。
【解决方案3】:

8.2.3.5 “允许处理器内转发”解释了存储缓冲区转发的示例:

最初 x = y = 0

    Processor 0             Processor 1
   ==============          =============
    mov [x], 1              mov [y], 1
    mov r1, [x]             mov r3, [y]
    mov r2, [y]             mov r4, [x]

r2 == 0r4 == 0 的结果是允许的。

...本例中的重新排序可能是存储缓冲区转发的结果。虽然存储暂时保存在处理器的存储缓冲区中,但它可以满足处理器自己的负载,但对其他处理器不可见(也无法满足)负载。

声明不能通过写入同一位置来重新排序读取(“读取可能会随着对不同位置的较旧写入而重新排序,但不能在较旧写入相同位置时重新排序”)位于适用于“用于定义为可回写高速缓存的内存区域的单处理器系统”。 “存储缓冲区转发”行为仅适用于多处理器行为。

【讨论】:

  • 我已经看到了,这个例子完全符合我的预期。但是我看不到它如何演示“读取传递[ing]写入相同的内存位置”。在这种情况下,读取和写入是并发的——它们开始时没有定义的顺序。我看不出一个超越另一个的意义。
  • @jacobsa:考虑加载r2。从处理器 0 的角度来看 - 它必须在写入 x 之后发生。同样在处理器 1 上,r4 的负载必须在写入y 之后发生。如果您不允许存储转发,那么如果 P0 将 y 读取为 0,那么 P0 的所有三个指令都必须在 P1 执行它的第一条指令之前执行。因此 P1 必须从 x 中读取 1 个。如果您考虑 P1 在不允许重新排序的情况下从位置 x 读取 0,则类似的逻辑适用。
  • 谢谢。我完全理解这个例子及其后果。我想我只是赶上了措辞,但我仍然看不到读取“传递”到同一内存位置的写入。此示例中的哪个内存位置,以及在什么意义上从写入的一侧开始读取并迁移到另一侧?据我所知,它们开始时是无序的(因为它们在不同的处理器上)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-07-19
  • 1970-01-01
  • 2010-10-13
  • 2020-07-21
  • 1970-01-01
  • 1970-01-01
  • 2013-02-09
相关资源
最近更新 更多