存储指令是否会在缓存未命中时阻止后续指令?

Cur*_*ous 5 c++ concurrency x86 cpu-architecture cpu-cache

假设我们有一个处理器,它有两个内核(C0 和 C1)和一个从k最初由 C0 拥有的地址开始的缓存线。如果 C1 在第 8 字节槽上发出一条存储指令k,是否会影响在 C1 上执行的后续指令的吞吐量?

intel优化手册有以下一段

当一条指令将数据写入内存位置 [...] 时,处理器会确保包含该内存位置的行位于其 L1d 缓存中 [...]。如果缓存线不存在,它会使用 RFO 请求 [...] RFO 从下一级获取数据,并在指令退出后存储数据。因此,存储延迟通常不会影响存储指令本身

参考以下代码,

// core c0
foo();
line(k)->at(i)->store(kConstant, std::memory_order_release);
bar();
baz();
Run Code Online (Sandbox Code Playgroud)

从英特尔手动使得报价我认为在上面的代码,代码的执行将看起来像是商店基本上是一个空操作,和结束之间会不会影响延迟foo()和开始bar()。相比之下,对于下面的代码,

// core c0
foo();
bar(line(k)->at(i)->load(std::memory_order_acquire));
baz();
Run Code Online (Sandbox Code Playgroud)

结束foo()和开始之间的延迟bar()会受到加载的影响,因为以下代码将加载结果作为依赖项。


这个问题主要与英特尔处理器(在 Broadwell 系列或更新版本中)如何在上述情况下工作有关。此外,特别是关于如何将类似于上述的 C++ 代码编译为这些处理器的汇编。

Bee*_*ope 6

一般来说,对于后续代码不会很快读取的存储,存储不会直接延迟任何现代乱序处理器(包括英特尔)上的后续代码。

例如:

foo()
*x = y;
bar()
Run Code Online (Sandbox Code Playgroud)

如果foo()不修改xy,并且bar不从 加载*x,则存储是独立的,甚至可能在foo()完成之前(甚至在它开始之前)就开始bar()执行,并且可能在存储提交到缓存之前bar()执行,甚至可能同时执行foo()正在运行等

虽然几乎没有直接影响,但这并不意味着没有间接影响,而且实际上商店可能会支配执行时间。

如果存储在缓存中未命中,则在满足缓存未命中时,它可能会占用核外资源。它通常还可以防止后续存储耗尽,这可能是一个瓶颈:如果存储缓冲区填满,前端会完全阻塞并且新指令不再进入调度程序。

最后,一切都取决于周围代码的细节,就像往常一样。如果该序列重复运行foo()并且bar()很短,则与存储相关的未命中可能会支配运行时间。毕竟,缓冲无法隐藏无限数量的商店的成本。在某些时候,您将受到商店的内在吞吐量的约束。

  • @Curious - 存储缓冲区的单位是“条目”,也称为单个存储。例如,如果存储缓冲区有 36 个条目,则它可以容纳 36 个存储,无论它们是否位于同一高速缓存行。每个存储微指令都需要一个存储缓冲区条目。 (2认同)
  • @Curious - 是的,没关系。无论如何,核心无法知道这一点,因此无论线路在哪里,存储都会以相同的方式进行。当它到达存储缓冲区的头部时,“未命中处理”将开始(或多或少),如果它位于另一个 NUMA 节点中,则可能需要比平常更长的时间,但在核心级别上没有根本差异。 (2认同)
  • 请注意,存储未命中会阻止后续存储提交,这意味着很可能会出现长时间未命中的问题。如果未命中需要 100 ns,则在 4 GHz cpu 上相当于 400 个周期,即 800 条指令,IPC 为 2。如果这 800 条指令的存储量超过“存储缓冲区大小”,则会停止运行。800 条指令具有 50-100 或更多存储的情况并不少见。@好奇的 (2认同)